diff options
Diffstat (limited to 'ipalib/aci.py')
-rwxr-xr-x | ipalib/aci.py | 251 |
1 files changed, 164 insertions, 87 deletions
diff --git a/ipalib/aci.py b/ipalib/aci.py index 9dde767c0..a9219f8dc 100755 --- a/ipalib/aci.py +++ b/ipalib/aci.py @@ -29,6 +29,16 @@ ACIPat = re.compile(r'\s*(\(.*\)+)\s*\(version\s+3.0\s*;\s*acl\s+\"(.*)\"\s*;\s* # Break the permissions/bind_rules out PermPat = re.compile(r'(\w+)\s*\((.*)\)\s+(.*)') +# Break the bind rule out +BindPat = re.compile(r'([a-zA-Z0-9;\.]+)\s*(\!?=)\s*(.*)') + +# Don't allow arbitrary attributes to be set in our __setattr__ implementation. +OBJECTATTRS = ["name", "orig_acistr", "target", "action", "permissions", + "bindrule"] +ACTIONS = ["allow", "deny"] + +PERMISSIONS = ["read", "write", "add", "delete", "search", "compare", + "selfwrite", "proxy", "all"] class ACI: """ @@ -36,23 +46,13 @@ class ACI: entry in LDAP. Has methods to parse an ACI string and export to an ACI String. """ - - # Don't allow arbitrary attributes to be set in our __setattr__ implementation. - _objectattrs = ["name", "orig_acistr", "target", "action", "permissions", - "bindrule"] - - __actions = ["allow", "deny"] - - __permissions = ["read", "write", "add", "delete", "search", "compare", - "selfwrite", "proxy", "all"] - def __init__(self,acistr=None): self.name = None self.orig_acistr = acistr self.target = {} self.action = "allow" self.permissions = ["write"] - self.bindrule = None + self.bindrule = {} if acistr is not None: self._parse_acistr(acistr) @@ -70,73 +70,21 @@ class ACI: """An alias for export_to_string()""" return self.export_to_string() - def __getattr__(self, name): - """ - Backward compatibility for the old ACI class. - - The following extra attributes are available: - - - source_group - - dest_group - - attrs - """ - if name == 'source_group': - group = '' - dn = self.bindrule.split('=',1) - if dn[0] == "groupdn": - group = self._remove_quotes(dn[1]) - if group.startswith("ldap:///"): - group = group[8:] - return group - if name == 'dest_group': - group = self.target.get('targetfilter', '') - if group: - g = group.split('=',1)[1] - if g.endswith(')'): - g = g[:-1] - return g - return '' - if name == 'attrs': - return self.target.get('targetattr', None) - raise AttributeError, "object has no attribute '%s'" % name - - def __setattr__(self, name, value): - """ - Backward compatibility for the old ACI class. - - The following extra attributes are available: - - source_group - - dest_group - - attrs - """ - if name == 'source_group': - self.__dict__['bindrule'] = 'groupdn="ldap:///%s"' % value - elif name == 'dest_group': - if value.startswith('('): - self.__dict__['target']['targetfilter'] = 'memberOf=%s' % value - else: - self.__dict__['target']['targetfilter'] = '(memberOf=%s)' % value - elif name == 'attrs': - self.__dict__['target']['targetattr'] = value - elif name in self._objectattrs: - self.__dict__[name] = value - else: - raise AttributeError, "object has no attribute '%s'" % name - def export_to_string(self): """Output a Directory Server-compatible ACI string""" self.validate() aci = "" for t in self.target: - if isinstance(self.target[t], list): + op = self.target[t]['operator'] + if isinstance(self.target[t]['expression'], list): target = "" - for l in self.target[t]: + for l in self.target[t]['expression']: target = target + l + " || " target = target[:-4] - aci = aci + "(%s=\"%s\")" % (t, target) + aci = aci + "(%s %s \"%s\")" % (t, op, target) else: - aci = aci + "(%s=\"%s\")" % (t, self.target[t]) - aci = aci + "(version 3.0;acl \"%s\";%s (%s) %s" % (self.name, self.action, ",".join(self.permissions), self.bindrule) + ";)" + aci = aci + "(%s %s \"%s\")" % (t, op, self.target[t]['expression']) + aci = aci + "(version 3.0;acl \"%s\";%s (%s) %s %s \"%s\"" % (self.name, self.action, ",".join(self.permissions), self.bindrule['keyword'], self.bindrule['operator'], self.bindrule['expression']) + ";)" return aci def _remove_quotes(self, s): @@ -154,13 +102,18 @@ class ACI: l = [] var = False + op = "=" for token in lexer: # We should have the form (a = b)(a = b)... if token == "(": var = lexer.next().strip() operator = lexer.next() if operator != "=" and operator != "!=": - raise SyntaxError('No operator in target, got %s' % operator) + # Peek at the next char before giving up + operator = operator + lexer.next() + if operator != "=" and operator != "!=": + raise SyntaxError("No operator in target, got '%s'" % operator) + op = operator val = lexer.next().strip() val = self._remove_quotes(val) end = lexer.next() @@ -169,10 +122,14 @@ class ACI: if var == 'targetattr': # Make a string of the form attr || attr || ... into a list - t = re.split('[\W]+', val) - self.target[var] = t + t = re.split('[^a-zA-Z0-9;\*]+', val) + self.target[var] = {} + self.target[var]['operator'] = op + self.target[var]['expression'] = t else: - self.target[var] = val + self.target[var] = {} + self.target[var]['operator'] = op + self.target[var]['expression'] = val def _parse_acistr(self, acistr): acimatch = ACIPat.match(acistr) @@ -184,8 +141,8 @@ class ACI: if not bindperms or len(bindperms.groups()) < 3: raise SyntaxError, "malformed ACI" self.action = bindperms.group(1) - self.permissions = bindperms.group(2).split(',') - self.bindrule = bindperms.group(3) + self.permissions = bindperms.group(2).replace(' ','').split(',') + self.set_bindrule(bindperms.group(3)) def validate(self): """Do some basic verification that this will produce a @@ -196,7 +153,7 @@ class ACI: if not isinstance(self.permissions, list): raise SyntaxError, "permissions must be a list" for p in self.permissions: - if not p.lower() in self.__permissions: + if not p.lower() in PERMISSIONS: raise SyntaxError, "invalid permission: '%s'" % p if not self.name: raise SyntaxError, "name must be set" @@ -204,14 +161,100 @@ class ACI: raise SyntaxError, "name must be a string" if not isinstance(self.target, dict) or len(self.target) == 0: raise SyntaxError, "target must be a non-empty dictionary" + if not isinstance(self.bindrule, dict): + raise SyntaxError, "bindrule must be a dictionary" + if not self.bindrule.get('operator') or not self.bindrule.get('keyword') or not self.bindrule.get('expression'): + raise SyntaxError, "bindrule is missing a component" + return True + + def set_target_filter(self, filter, operator="="): + self.target['targetfilter'] = {} + if not filter.startswith("("): + filter = "(" + filter + ")" + self.target['targetfilter']['expression'] = filter + self.target['targetfilter']['operator'] = operator + + def set_target_attr(self, attr, operator="="): + if not isinstance(attr, list): + attr = [attr] + self.target['targetattr'] = {} + self.target['targetattr']['expression'] = attr + self.target['targetattr']['operator'] = operator + + def set_target(self, target, operator="="): + assert target.startswith("ldap:///") + self.target['target'] = {} + self.target['target']['expression'] = target + self.target['target']['operator'] = operator + + def set_bindrule(self, bindrule): + match = BindPat.match(bindrule) + if not match or len(match.groups()) < 3: + raise SyntaxError, "malformed bind rule" + self.set_bindrule_keyword(match.group(1)) + self.set_bindrule_operator(match.group(2)) + self.set_bindrule_expression(match.group(3).replace('"','')) + + def set_bindrule_keyword(self, keyword): + self.bindrule['keyword'] = keyword + + def set_bindrule_operator(self, operator): + self.bindrule['operator'] = operator + + def set_bindrule_expression(self, expression): + self.bindrule['expression'] = expression + + def isequal(self, b): + """ + Compare the current ACI to another one to see if they are + the same. + + returns True if equal, False if not. + """ + try: + if self.name != b.name: + return False + + if set(self.permissions) != set(b.permissions): + return False + + if self.bindrule.get('keyword') != b.bindrule.get('keyword'): + return False + if self.bindrule.get('operator') != b.bindrule.get('operator'): + return False + if self.bindrule.get('expression') != b.bindrule.get('expression'): + return False + + if self.target.get('targetfilter',{}).get('expression') != b.target.get('targetfilter',{}).get('expression'): + return False + if self.target.get('targetfilter',{}).get('operator') != b.target.get('targetfilter',{}).get('operator'): + return False + + if set(self.target.get('targetattr',{}).get('expression')) != set(b.target.get('targetattr',{}).get('expression')): + return False + if self.target.get('targetattr',{}).get('operator') != b.target.get('targetattr',{}).get('operator'): + return False + + if self.target.get('target',{}).get('expression') != b.target.get('target',{}).get('expression'): + return False + if self.target.get('target',{}).get('operator') != b.target.get('target',{}).get('operator'): + return False + + except Exception: + # If anything throws up then they are not equal + return False + + # We got this far so lets declare them the same return True def extract_group_cns(aci_list, client): - """Extracts all the cn's from a list of aci's and returns them as a hash - from group_dn to group_cn. + """ + Extracts all the cn's from a list of aci's and returns them as a hash + from group_dn to group_cn. - It first tries to cheat by looking at the first rdn for the - group dn. If that's not cn for some reason, it looks up the group.""" + It first tries to cheat by looking at the first rdn for the + group dn. If that's not cn for some reason, it looks up the group. + """ group_dn_to_cn = {} for aci in aci_list: for dn in (aci.source_group, aci.dest_group): @@ -231,15 +274,49 @@ def extract_group_cns(aci_list, client): return group_dn_to_cn if __name__ == '__main__': - # Pass in an ACI as a string - a = ACI('(targetattr="title")(targetfilter="(memberOf=cn=bar,cn=groups,cn=accounts ,dc=example,dc=com)")(version 3.0;acl "foobar";allow (write) groupdn="ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com";)') +# a = ACI('(targetattr="title")(targetfilter="(memberOf=cn=bar,cn=groups,cn=accounts ,dc=example,dc=com)")(version 3.0;acl "foobar";allow (write) groupdn="ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com";)') +# print a +# a = ACI('(target="ldap:///uid=bjensen,dc=example,dc=com")(targetattr=*) (version 3.0;acl "aci1";allow (write) userdn="ldap:///self";)') +# print a +# a = ACI(' (targetattr = "givenName || sn || cn || displayName || title || initials || loginShell || gecos || homePhone || mobile || pager || facsimileTelephoneNumber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || secretary || description || carLicense || labeledURI || inetUserHTTPURL || seeAlso || employeeType || businessCategory || ou")(version 3.0;acl "Self service";allow (write) userdn = "ldap:///self";)') +# print a + + a = ACI('(target="ldap:///uid=*,cn=users,cn=accounts,dc=example,dc=com")(version 3.0;acl "add_user";allow (add) groupdn="ldap:///cn=add_user,cn=taskgroups,dc=example,dc=com";)') + print a + print "---" + + a = ACI('(targetattr=member)(target="ldap:///cn=ipausers,cn=groups,cn=accounts,dc=example,dc=com")(version 3.0;acl "add_user_to_default_group";allow (write) groupdn="ldap:///cn=add_user_to_default_group,cn=taskgroups,dc=example,dc=com";)') + print a + print "---" + + a = ACI('(targetattr!=member)(target="ldap:///cn=ipausers,cn=groups,cn=accounts,dc=example,dc=com")(version 3.0;acl "add_user_to_default_group";allow (write) groupdn="ldap:///cn=add_user_to_default_group,cn=taskgroups,dc=example,dc=com";)') print a + print "---" + + a = ACI('(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "change_password"; allow (write) groupdn = "ldap:///cn=change_password,cn=taskgroups,dc=example,dc=com";)') + print a + print "---" - # Create an ACI in pieces a = ACI() - a.name ="foobar" - a.source_group="cn=foo,cn=groups,dc=example,dc=org" - a.dest_group="cn=bar,cn=groups,dc=example,dc=org" - a.attrs = ['title'] + a.name ="foo" + a.set_target_attr(['title','givenname'], "!=") +# a.set_bindrule("groupdn = \"ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com\"") + a.set_bindrule_keyword("groupdn") + a.set_bindrule_operator("=") + a.set_bindrule_expression ("\"ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com\"") a.permissions = ['read','write','add'] print a + + b = ACI() + b.name ="foo" + b.set_target_attr(['givenname','title'], "!=") + b.set_bindrule_keyword("groupdn") + b.set_bindrule_operator("=") + b.set_bindrule_expression ("\"ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com\"") + b.permissions = ['add','read','write'] + print b + + print a.isequal(b) + + a = ACI('(targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory || krbMKey")(version 3.0; acl "Enable Anonymous access"; allow (read, search, compare) userdn = "ldap:///anyone";)') + print a |