py.lib
2022-07-17
Parent:1668cc57225b
py.lib/ldap_utils/ldap_passwd_changer.py
. Исправление в форматировании
| awgur@13 | 1 # coding: utf-8 |
| awgur@13 | 2 """ |
| awgur@13 | 3 Смена пароля на пользователя в AD посредством Python |
| awgur@13 | 4 |
| awgur@13 | 5 """ |
| awgur@13 | 6 |
| awgur@13 | 7 from ldap3 import Server, Connection, SIMPLE, SUBTREE |
| awgur@13 | 8 from ldap3.core.exceptions import LDAPException |
| awgur@13 | 9 from secrets import token_urlsafe |
| awgur@13 | 10 import string |
| awgur@13 | 11 |
| awgur@23 | 12 |
| awgur@13 | 13 class LdapError(Exception): |
| awgur@13 | 14 @staticmethod |
| awgur@23 | 15 def err_fmt(e): |
| awgur@23 | 16 _buf = ' '.join(str(e).split()) # всё в одну строку с одним пробелом |
| awgur@23 | 17 # между словами |
| awgur@13 | 18 return "%s(%s)" % (type(e).__name__, _buf) |
| awgur@23 | 19 |
| awgur@23 | 20 |
| awgur@23 | 21 class LdapServerError(LdapError): |
| awgur@23 | 22 pass |
| awgur@23 | 23 |
| awgur@23 | 24 |
| awgur@23 | 25 class LdapPassGenError(LdapError): |
| awgur@23 | 26 pass |
| awgur@23 | 27 |
| awgur@23 | 28 |
| awgur@23 | 29 class LdapNotFound(LdapError): |
| awgur@23 | 30 pass |
| awgur@13 | 31 |
| awgur@13 | 32 |
| awgur@13 | 33 class LdapPasswdChanger(object): |
| awgur@23 | 34 def __init__(self, ldap_server_uri, ldap_user, ldap_passwd, ldap_base_dn, ldap_timeout=120, ldap_reconnect=5, |
| awgur@23 | 35 passwd_len=25): |
| awgur@13 | 36 self.ldap_server_uri = ldap_server_uri # URI к серверу: ldaps://server_name:port_number |
| awgur@23 | 37 self.ldap_user = ldap_user # Пользователь, имеющий право сброса паролей |
| awgur@23 | 38 self.ldap_passwd = ldap_passwd # Пароль к тому пользователю |
| awgur@23 | 39 self.ldap_base_dn = ldap_base_dn |
| awgur@23 | 40 self.ldap_timeout = ldap_timeout # Таймаут операций с сервером |
| awgur@23 | 41 self.passwd_len = passwd_len # Желаемая длинна пароля |
| awgur@23 | 42 self.ldap_reconnect = ldap_reconnect # Количество попыток переподключения к серверу |
| awgur@13 | 43 |
| awgur@13 | 44 # Заготовка под пароли |
| awgur@13 | 45 self._set_punct = set(string.punctuation) |
| awgur@13 | 46 self._set_uper = set(string.ascii_uppercase) |
| awgur@13 | 47 self._set_lower = set(string.ascii_lowercase) |
| awgur@13 | 48 |
| awgur@23 | 49 def get_passwd(self): |
| awgur@13 | 50 _cnt = 0 |
| awgur@13 | 51 while True: |
| awgur@13 | 52 res = token_urlsafe(self.passwd_len) |
| awgur@13 | 53 _res_set = set(res) |
| awgur@23 | 54 if ( |
| awgur@23 | 55 (_res_set & self._set_punct) |
| awgur@23 | 56 and (_res_set & self._set_uper) |
| awgur@23 | 57 and (_res_set & self._set_lower) |
| awgur@13 | 58 ): |
| awgur@13 | 59 return res |
| awgur@13 | 60 |
| awgur@13 | 61 _cnt += 1 |
| awgur@13 | 62 if _cnt > 60: |
| awgur@13 | 63 raise LdapPassGenError('Не удалось сгенерировать пароль') |
| awgur@13 | 64 |
| awgur@23 | 65 def change_passwd(self, login): |
| awgur@13 | 66 _reconnects = self.ldap_reconnect |
| awgur@13 | 67 if _reconnects is None or _reconnects < 1: |
| awgur@13 | 68 _reconnects = 1 |
| awgur@13 | 69 |
| awgur@13 | 70 while True: |
| awgur@13 | 71 _reconnects -= 1 |
| awgur@13 | 72 try: |
| awgur@23 | 73 ldap_srv = Server(self.ldap_server_uri, connect_timeout=self.ldap_timeout) |
| awgur@23 | 74 ldap_conn = Connection(ldap_srv, authentication=SIMPLE, |
| awgur@23 | 75 user=self.ldap_user, password=self.ldap_passwd, |
| awgur@23 | 76 check_names=True, |
| awgur@23 | 77 auto_referrals=False, raise_exceptions=True, auto_range=True, |
| awgur@23 | 78 ) |
| awgur@23 | 79 ldap_conn.open() |
| awgur@23 | 80 if not ldap_conn.bind(): |
| awgur@13 | 81 continue |
| awgur@13 | 82 |
| awgur@13 | 83 break |
| awgur@23 | 84 |
| awgur@13 | 85 except LDAPException as e: |
| awgur@13 | 86 if not _reconnects > 0: |
| awgur@23 | 87 raise LdapServerError(f'Ошибка подключения к серверу: {LdapServerError.err_fmt(e)}') |
| awgur@13 | 88 |
| awgur@23 | 89 filter = f'(sAMAccountName={login})' |
| awgur@13 | 90 try: |
| awgur@23 | 91 if not ldap_conn.search(self.ldap_base_dn, filter): |
| awgur@23 | 92 raise LdapServerError('Сервер вернул статус ошибки: %(desc)s(%(code)s) - %(msg)s' % { |
| awgur@23 | 93 'desc': ldap_conn.result['description'], |
| awgur@23 | 94 'code': ldap_conn.result['result'], |
| awgur@23 | 95 'msg': ldap_conn.result['message'], |
| awgur@23 | 96 }) |
| awgur@23 | 97 |
| awgur@13 | 98 except LDAPException as e: |
| awgur@23 | 99 raise LdapServerError(f'Ошибка при поиска УЗ в ldap: {LdapServerError.err_fmt(e)}') |
| awgur@13 | 100 |
| awgur@23 | 101 if not len(ldap_conn.entries) > 0: |
| awgur@23 | 102 raise LdapNotFound(f'Не найдена учётная запись: {login}') |
| awgur@23 | 103 |
| awgur@23 | 104 elif len(ldap_conn.entries) != 1: |
| awgur@23 | 105 _found_names = '; '.join([i.entry_dn for i in ldap_conn.entries[:10]]) |
| awgur@13 | 106 |
| awgur@23 | 107 raise LdapError(f'Найдено более одной УЗ по заданному имени {login}: {len(ldap_conn.entries)} ' |
| awgur@23 | 108 f'- {_found_names}') |
| awgur@23 | 109 |
| awgur@23 | 110 login_dn = ldap_conn.entries[0].entry_dn |
| awgur@23 | 111 new_passwd = self.get_passwd() |
| awgur@13 | 112 try: |
| awgur@23 | 113 ldap_conn.extend.microsoft.modify_password(login_dn, new_password=new_passwd, old_password=None) |
| awgur@23 | 114 |
| awgur@13 | 115 except LDAPException as e: |
| awgur@23 | 116 raise LdapServerError('Ошибка при смене пароля на УЗ: %s' % LdapServerError.err_fmt(e)) |
| awgur@13 | 117 |
| awgur@13 | 118 return new_passwd |