1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net> 5# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com> 6# 7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 8 9from __future__ import absolute_import, division, print_function 10__metaclass__ = type 11 12 13DOCUMENTATION = ''' 14--- 15module: ldap_entry 16short_description: Add or remove LDAP entries. 17description: 18 - Add or remove LDAP entries. This module only asserts the existence or 19 non-existence of an LDAP entry, not its attributes. To assert the 20 attribute values of an entry, see M(community.general.ldap_attrs). 21notes: 22 - The default authentication settings will attempt to use a SASL EXTERNAL 23 bind over a UNIX domain socket. This works well with the default Ubuntu 24 install for example, which includes a cn=peercred,cn=external,cn=auth ACL 25 rule allowing root to modify the server configuration. If you need to use 26 a simple bind to access your server, pass the credentials in I(bind_dn) 27 and I(bind_pw). 28author: 29 - Jiri Tyr (@jtyr) 30requirements: 31 - python-ldap 32options: 33 attributes: 34 description: 35 - If I(state=present), attributes necessary to create an entry. Existing 36 entries are never modified. To assert specific attribute values on an 37 existing entry, use M(community.general.ldap_attrs) module instead. 38 type: dict 39 objectClass: 40 description: 41 - If I(state=present), value or list of values to use when creating 42 the entry. It can either be a string or an actual list of 43 strings. 44 type: list 45 elements: str 46 state: 47 description: 48 - The target state of the entry. 49 choices: [present, absent] 50 default: present 51 type: str 52extends_documentation_fragment: 53- community.general.ldap.documentation 54 55''' 56 57 58EXAMPLES = """ 59- name: Make sure we have a parent entry for users 60 community.general.ldap_entry: 61 dn: ou=users,dc=example,dc=com 62 objectClass: organizationalUnit 63 64- name: Make sure we have an admin user 65 community.general.ldap_entry: 66 dn: cn=admin,dc=example,dc=com 67 objectClass: 68 - simpleSecurityObject 69 - organizationalRole 70 attributes: 71 description: An LDAP administrator 72 userPassword: "{SSHA}tabyipcHzhwESzRaGA7oQ/SDoBZQOGND" 73 74- name: Get rid of an old entry 75 community.general.ldap_entry: 76 dn: ou=stuff,dc=example,dc=com 77 state: absent 78 server_uri: ldap://localhost/ 79 bind_dn: cn=admin,dc=example,dc=com 80 bind_pw: password 81 82# 83# The same as in the previous example but with the authentication details 84# stored in the ldap_auth variable: 85# 86# ldap_auth: 87# server_uri: ldap://localhost/ 88# bind_dn: cn=admin,dc=example,dc=com 89# bind_pw: password 90# 91# In the example below, 'args' is a task keyword, passed at the same level as the module 92- name: Get rid of an old entry 93 community.general.ldap_entry: 94 dn: ou=stuff,dc=example,dc=com 95 state: absent 96 args: "{{ ldap_auth }}" 97""" 98 99 100RETURN = """ 101# Default return values 102""" 103 104import traceback 105 106from ansible.module_utils.basic import AnsibleModule, missing_required_lib 107from ansible.module_utils.common.text.converters import to_native, to_bytes 108from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs 109 110LDAP_IMP_ERR = None 111try: 112 import ldap.modlist 113 114 HAS_LDAP = True 115except ImportError: 116 LDAP_IMP_ERR = traceback.format_exc() 117 HAS_LDAP = False 118 119 120class LdapEntry(LdapGeneric): 121 def __init__(self, module): 122 LdapGeneric.__init__(self, module) 123 124 # Shortcuts 125 self.state = self.module.params['state'] 126 127 # Add the objectClass into the list of attributes 128 self.module.params['attributes']['objectClass'] = ( 129 self.module.params['objectClass']) 130 131 # Load attributes 132 if self.state == 'present': 133 self.attrs = self._load_attrs() 134 135 def _load_attrs(self): 136 """ Turn attribute's value to array. """ 137 attrs = {} 138 139 for name, value in self.module.params['attributes'].items(): 140 if isinstance(value, list): 141 attrs[name] = list(map(to_bytes, value)) 142 else: 143 attrs[name] = [to_bytes(value)] 144 145 return attrs 146 147 def add(self): 148 """ If self.dn does not exist, returns a callable that will add it. """ 149 def _add(): 150 self.connection.add_s(self.dn, modlist) 151 152 if not self._is_entry_present(): 153 modlist = ldap.modlist.addModlist(self.attrs) 154 action = _add 155 else: 156 action = None 157 158 return action 159 160 def delete(self): 161 """ If self.dn exists, returns a callable that will delete it. """ 162 def _delete(): 163 self.connection.delete_s(self.dn) 164 165 if self._is_entry_present(): 166 action = _delete 167 else: 168 action = None 169 170 return action 171 172 def _is_entry_present(self): 173 try: 174 self.connection.search_s(self.dn, ldap.SCOPE_BASE) 175 except ldap.NO_SUCH_OBJECT: 176 is_present = False 177 else: 178 is_present = True 179 180 return is_present 181 182 183def main(): 184 module = AnsibleModule( 185 argument_spec=gen_specs( 186 attributes=dict(default={}, type='dict'), 187 objectClass=dict(type='list', elements='str'), 188 state=dict(default='present', choices=['present', 'absent']), 189 ), 190 required_if=[('state', 'present', ['objectClass'])], 191 supports_check_mode=True, 192 ) 193 194 if not HAS_LDAP: 195 module.fail_json(msg=missing_required_lib('python-ldap'), 196 exception=LDAP_IMP_ERR) 197 198 state = module.params['state'] 199 200 # Instantiate the LdapEntry object 201 ldap = LdapEntry(module) 202 203 # Get the action function 204 if state == 'present': 205 action = ldap.add() 206 elif state == 'absent': 207 action = ldap.delete() 208 209 # Perform the action 210 if action is not None and not module.check_mode: 211 try: 212 action() 213 except Exception as e: 214 module.fail_json(msg="Entry action failed.", details=to_native(e), exception=traceback.format_exc()) 215 216 module.exit_json(changed=(action is not None)) 217 218 219if __name__ == '__main__': 220 main() 221