From 55c62ac79af235b75f969434aba775c4a8c30274 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 4 Nov 2009 15:56:43 -0500 Subject: Add support for setting/adding arbitrary attributes This introduces 2 new params: --setattr and --addattr Both take a name/value pair, ala: ipa user-mod --setattr=postalcode=20601 jsmith --setattr replaces or sets the current attribute to the value --addattr adds the value to an attribute (or sets a new attribute) OptionsParser allows multiple versions of this, so you can have multiple setattr and addattr, either for the same attribute or for different attributes. ipa user-mod --addattr=postalcode=20601 --addattr=postalcode=30330 jsmith Values are silent dropped if either of these on an existing param: ipa user-mod --setattr=givenname=Jerry jsmith Is a no-op. --- ipalib/frontend.py | 45 ++++++++++++++++++++++++++++++++++ ipalib/plugins/baseldap.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/ipalib/frontend.py b/ipalib/frontend.py index b13ffed42..e257a0a25 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -473,6 +473,34 @@ class Command(HasParam): kw = self.args_options_2_params(*args, **options) return dict(self.__attributes_2_entry(kw)) + def __convert_2_dict(self, attrs, append=True): + """ + Convert a string in the form of name/value pairs into + a dictionary. The incoming attribute may be a string or + a list. + + Any attribute found that is also a param is silently dropped. + + append controls whether this returns a list of values or a single + value. + """ + newdict = {} + if not type(attrs) in (list, tuple): + attrs = [attrs] + for a in attrs: + m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", a) + attr = str(m.group(1)).lower() + value = m.group(2) + if len(value) == 0: + # None means "delete this attribute" + value = None + if attr not in self.params: + if append and attr in newdict: + newdict[attr].append(value) + else: + newdict[attr] = [value] + return newdict + def __attributes_2_entry(self, kw): for name in self.params: if self.params[name].attribute and name in kw: @@ -482,6 +510,23 @@ class Command(HasParam): else: yield (name, kw[name]) + adddict = {} + if 'setattr' in kw: + adddict = self.__convert_2_dict(kw['setattr'], append=False) + + if 'addattr' in kw: + adddict.update(self.__convert_2_dict(kw['addattr'])) + + for name in adddict: + value = adddict[name] + if isinstance(value, list): + if len(value) == 1: + yield (name, value[0]) + else: + yield (name, [v for v in value]) + else: + yield (name, value) + def params_2_args_options(self, **params): """ Split params into (args, options). diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 3798d8388..b0ea57380 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -20,6 +20,7 @@ Base classes for LDAP plugins. """ +import re from ipalib import crud, errors, uuid from ipalib import Command, Method, Object from ipalib import Flag, List, Str @@ -27,6 +28,28 @@ from ipalib.base import NameSpace from ipalib.cli import to_cli, from_cli +def validate_add_attribute(ugettext, attr): + validate_attribute(ugettext, 'addattr', attr) + +def validate_set_attribute(ugettext, attr): + validate_attribute(ugettext, 'setattr', attr) + +def validate_attribute(ugettext, name, attr): + m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", attr) + if not m or len(m.groups()) != 2: + raise errors.ValidationError(name=name, error='Invalid format. Should be name=value') + +def get_attributes(attrs): + """ + Given a list of values in the form name=value, return a list of name. + """ + attrlist=[] + for attr in attrs: + m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", attr) + attrlist.append(str(m.group(1)).lower()) + + return attrlist + class LDAPObject(Object): """ Object representing a LDAP entry. @@ -111,6 +134,16 @@ class LDAPCreate(crud.Create): doc='print entries as they are stored in LDAP', exclude='webui', ), + Str('addattr*', validate_add_attribute, + cli_name='addattr', + doc='Add an attribute/value pair. Format is attr=value', + exclude='webui', + ), + Str('setattr*', validate_set_attribute, + cli_name='setattr', + doc='Set an attribute to an name/value pair. Format is attr=value', + exclude='webui', + ), ) def get_args(self): @@ -241,6 +274,16 @@ class LDAPUpdate(LDAPQuery, crud.Update): cli_name='all', doc='retrieve all attributes', ), + Str('addattr*', validate_add_attribute, + cli_name='addattr', + doc='Add an attribute/value pair. Format is attr=value', + exclude='webui', + ), + Str('setattr*', validate_set_attribute, + cli_name='setattr', + doc='Set an attribute to an name/value pair. Format is attr=value', + exclude='webui', + ), ) def execute(self, *keys, **options): @@ -257,6 +300,24 @@ class LDAPUpdate(LDAPQuery, crud.Update): dn = self.pre_callback(ldap, dn, entry_attrs, attrs_list, *keys, **options) + """ + Some special handling is needed because we need to update the + values here rather than letting ldap.update_entry() do the work. We + have to do the work of adding new values to an existing attribute + because if we pass just what is addded only the new values get + set. + """ + if 'addattr' in options: + (dn, old_entry) = ldap.get_entry(dn, attrs_list) + attrlist = get_attributes(options['addattr']) + for attr in attrlist: + if attr in old_entry: + if type(entry_attrs[attr]) in (tuple,list): + entry_attrs[attr] = old_entry[attr] + entry_attrs[attr] + else: + old_entry[attr].append(entry_attrs[attr]) + entry_attrs[attr] = old_entry[attr] + try: ldap.update_entry(dn, entry_attrs) except errors.EmptyModlist: -- cgit