diff --git a/roles/ldap_server/library/ldap_entry.py b/roles/ldap_server/library/ldap_entry.py index 51720acfb5ee0b107541337d8efad61ab0b4e7f7..3e23908ac84b82eff764de068b24fa4ebaff7e78 100755 --- a/roles/ldap_server/library/ldap_entry.py +++ b/roles/ldap_server/library/ldap_entry.py @@ -21,10 +21,13 @@ options: default: "" state: description: - - LDAP entry state. + - LDAP entry state. State C(present) requires that all entry attributes + are listed. If you wish to add some attributes, with specific values, to + existing entry, use state C(addattributes). If you wish to replace + existing values for an attribute, use C(replaceattributes). required: true default: "present" - choices: [ "present", "absent" ] + choices: [ "present", "absent", "addattributes", "replaceattributes" ] server_uri: description: - LDAP connection URI specifying what server to connect to. @@ -50,7 +53,9 @@ options: string (for one value of an attribute), for storing multiple values for same attribute. If providing a base64-encoded value, prefix it with C(base64:) (this is useful for I(usercertificate;binary) or - I(displayName) attributes). + I(displayName) attributes). In order to remove an attribute, set its + value to an empty string (C("")), and set the state to + C(replaceattributes). required: false default: "" """ @@ -78,6 +83,24 @@ ldap_entry: mail: john.doe@example.com mobile: +1 11 111 111 11 usercertificate;binary: base64:MIIC...lotsofcharacters...+/A== + +# Add attribute to an entry. +ldap_entry: + dn: uid=john,ou=people,dc=example,dc=com + state: addattributes + mail: john.doe@example.com + +# Make sure the configuration database has specific logging level enabled. +ldap_entry: + dn: cn=config + state: replaceattributes + olcLogLevel: 256 + +# Remove attribute from an entry. +ldap_entry: + dn: uid=john,ou=people,dc=example,dc=com + state: replaceattributes + uid: "" """ @@ -91,6 +114,8 @@ except ImportError: else: ldap_found = True +from copy import deepcopy + def get_ldap_connection(uri, bind_dn=None, bind_password=""): """ @@ -187,6 +212,68 @@ class LDAPEntry(object): return False + def addattributes(self): + """ + Adds attributes to an existing entry. + + Returns: + + True, if entry was updated with new attribute values. False if no change + was necessary (values are already present). + """ + + if not self.exists(): + raise Exception("Module error: Can't add attributes for an entry. Entry does not exist.") + + attribute_list = self.attributes.keys() + + current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE, attrlist=attribute_list)[0][1] + new_attributes = deepcopy(self.attributes) + + for attribute, values in current_attributes.iteritems(): + if attribute in new_attributes: + new_attributes[attribute].extend(values) + new_attributes[attribute] = list(set(new_attributes[attribute])) + else: + new_attributes[attribute] = values + + modification_list = ldap.modlist.modifyModlist(current_attributes, + new_attributes) + + if not modification_list: + return False + + self.connection.modify_s(self.dn, modification_list) + + return True + + def replaceattributes(self): + """ + Replace attributes of an existing entry. + + Returns: + + True, if entry was updated with new attribute values. False if no change + was necessary (values are already present). + """ + + if not self.exists(): + raise Exception("Module error: Can't replace attributes for an entry. Entry does not exist.") + + attribute_list = self.attributes.keys() + + current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE, attrlist=attribute_list)[0][1] + + modification_list = ldap.modlist.modifyModlist(current_attributes, + self.attributes, ignore_oldexistent=1) + + if not modification_list: + return False + + self.connection.modify_s(self.dn, modification_list) + + return True + def _update(self): """ Updates an LDAP entry to have the requested attributes. @@ -197,8 +284,8 @@ class LDAPEntry(object): already has the correct attributes). """ - self.current_attributes = self.connection.search_s(self.dn, - ldap.SCOPE_BASE)[0][1] + self.current_attributes = self.connection.search_s(self.dn, ldap.SCOPE_BASE)[0][1] + modification_list = ldap.modlist.modifyModlist(self.current_attributes, self.attributes) @@ -235,7 +322,7 @@ def main(): module = AnsibleModule( argument_spec=dict( dn=dict(required=True), - state=dict(required=False, choices=["present", "absent"], default="present"), + state=dict(required=False, choices=["present", "absent", "addattributes", "replaceattributes"], default="present"), server_uri=dict(required=False, default="ldapi:///"), bind_dn=dict(required=False, default=None), bind_password=dict(required=False) @@ -250,7 +337,9 @@ def main(): attributes = {} for name, value in module.params.iteritems(): if name not in module.argument_spec: - if isinstance(value, basestring): + if value == "": + pass + elif isinstance(value, basestring): value = [ value.encode("utf-8") ] elif isinstance(value, list): value = [ i.encode("utf-8") for i in value ] @@ -262,6 +351,7 @@ def main(): module.params["bind_password"]) except ldap.LDAPError as e: module.fail_json(msg="LDAP error: %s" % str(e)) + state = module.params["state"] entry = LDAPEntry(module.params["dn"], attributes, @@ -269,8 +359,12 @@ def main(): # Add/remove entry as requested. try: - if module.params["state"] == "present": + if state == "present": changed = entry.add() + elif state == "addattributes": + changed = entry.addattributes() + elif state == "replaceattributes": + changed = entry.replaceattributes() else: changed = entry.remove() except ldap.LDAPError as e: