py.lib

Yohn Y. 2023-01-28 Parent:1668cc57225b

43:6f8bea109183 Go to Latest

py.lib/ldap_utils/ldap_passwd_changer.py

. Наведение порядка в коде логирования

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