Skip to content

Commit 2f7c27e

Browse files
fix:unsupported hash type MD4
1 parent e0fb386 commit 2f7c27e

File tree

2 files changed

+162
-58
lines changed

2 files changed

+162
-58
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
shuffle-sdk
22
ldap3==2.9.1
3+
pycryptodome

active-directory/1.0.0/src/app.py

Lines changed: 161 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import json
2+
import hashlib
23
import ldap3
34
import asyncio
4-
from ldap3 import (
5-
Server,
6-
Connection,
7-
MODIFY_REPLACE,
8-
ALL_ATTRIBUTES,
9-
NTLM
10-
)
5+
from ldap3 import Server, Connection, MODIFY_REPLACE, ALL_ATTRIBUTES, NTLM
6+
7+
try:
8+
from Crypto.Hash import MD4 as CryptoMD4
9+
except ImportError:
10+
CryptoMD4 = None
1111

12-
from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups
13-
from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersFromGroups
12+
from ldap3.extend.microsoft.addMembersToGroups import (
13+
ad_add_members_to_groups as addUsersInGroups,
14+
)
15+
from ldap3.extend.microsoft.removeMembersFromGroups import (
16+
ad_remove_members_from_groups as removeUsersFromGroups,
17+
)
1418

1519
from shuffle_sdk import AppBase
1620

21+
1722
class ActiveDirectory(AppBase):
1823
__version__ = "1.0.1"
1924
app_name = "Active Directory" # this needs to match "name" in api.yaml
@@ -28,11 +33,28 @@ def __init__(self, redis, logger, console_logger=None):
2833
super().__init__(redis, logger, console_logger)
2934

3035
def __ldap_connection(self, server, port, domain, login_user, password, use_ssl):
31-
use_SSL = False if use_ssl.lower() == "false" else True
36+
use_SSL = False if use_ssl.lower() == "false" else True
3237
login_dn = domain + "\\" + login_user
3338

3439
s = Server(server, port=int(port), use_ssl=use_SSL)
35-
c = Connection(s, user=login_dn, password=password, authentication=NTLM, auto_bind=True)
40+
41+
if CryptoMD4 and not getattr(hashlib, "__active_directory_md4_patch__", False):
42+
try:
43+
import ldap3.utils.ntlm as ldap3_ntlm
44+
45+
def _md4_hash(data):
46+
md4 = CryptoMD4.new()
47+
md4.update(data)
48+
return md4.digest()
49+
50+
ldap3_ntlm.hashlib.md4 = _md4_hash
51+
hashlib.__active_directory_md4_patch__ = True
52+
except Exception:
53+
pass
54+
55+
c = Connection(
56+
s, user=login_dn, password=password, authentication=NTLM, auto_bind=True
57+
)
3658
return c
3759

3860
# Decode UserAccountControl code
@@ -137,21 +159,28 @@ def user_attributes(
137159

138160
result = json.loads(c.response_to_json())
139161
if len(result["entries"]) == 0:
140-
return json.dumps({
141-
"success": False,
142-
"result": result,
143-
"reason": "No user found for %s" % samaccountname,
144-
})
162+
return json.dumps(
163+
{
164+
"success": False,
165+
"result": result,
166+
"reason": "No user found for %s" % samaccountname,
167+
}
168+
)
145169

146170
except Exception as e:
147-
return json.dumps({
148-
"success": False,
149-
"reason": "Failed to get users in user attributes: %s" % e,
150-
})
151-
171+
return json.dumps(
172+
{
173+
"success": False,
174+
"reason": "Failed to get users in user attributes: %s" % e,
175+
}
176+
)
152177

153178
result = result["entries"][0]
154-
result["attributes"]["userAccountControl"] = self.__getUserAccountControlAttributes(result["attributes"]["userAccountControl"])
179+
result["attributes"]["userAccountControl"] = (
180+
self.__getUserAccountControlAttributes(
181+
result["attributes"]["userAccountControl"]
182+
)
183+
)
155184

156185
return json.dumps(result)
157186

@@ -180,7 +209,19 @@ def set_password(
180209
server, port, domain, login_user, password, use_ssl
181210
)
182211

183-
result = json.loads( self.user_attributes( server, port, domain, login_user, password, base_dn, use_ssl, samaccountname, search_base,))
212+
result = json.loads(
213+
self.user_attributes(
214+
server,
215+
port,
216+
domain,
217+
login_user,
218+
password,
219+
base_dn,
220+
use_ssl,
221+
samaccountname,
222+
search_base,
223+
)
224+
)
184225

185226
user_dn = result["dn"]
186227
c.extend.microsoft.modify_password(user_dn, new_password)
@@ -243,7 +284,6 @@ def enable_user(
243284
samaccountname,
244285
search_base,
245286
):
246-
247287
if search_base:
248288
base_dn = search_base
249289

@@ -299,7 +339,6 @@ def disable_user(
299339
samaccountname,
300340
search_base,
301341
):
302-
303342
if search_base:
304343
base_dn = search_base
305344

@@ -326,7 +365,6 @@ def disable_user(
326365
"success": False,
327366
"reason": "Failed to get result attributes: %s" % e,
328367
}
329-
330368

331369
if "ACCOUNTDISABLED" in userAccountControl:
332370
try:
@@ -362,8 +400,18 @@ def disable_user(
362400
"reason": "Failed adding ACCOUNTDISABLED to user: %s" % e,
363401
}
364402

365-
def lock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base):
366-
403+
def lock_user(
404+
self,
405+
server,
406+
domain,
407+
port,
408+
login_user,
409+
password,
410+
base_dn,
411+
use_ssl,
412+
samaccountname,
413+
search_base,
414+
):
367415
if search_base:
368416
base_dn = search_base
369417

@@ -372,19 +420,29 @@ def lock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samacc
372420
c.search(base_dn, f"(SAMAccountName={samaccountname})")
373421

374422
if len(c.entries) == 0:
375-
return {"success":"false","message":f"User {samaccountname} not found"}
423+
return {"success": "false", "message": f"User {samaccountname} not found"}
376424

377425
user_dn = c.entries[0].entry_dn
378426

379-
c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[514])]})
427+
c.modify(user_dn, {"userAccountControl": [(MODIFY_REPLACE, [514])]})
380428

381429
result = c.result
382430
result["success"] = True
383431

384432
return result
385-
386-
def unlock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base):
387-
433+
434+
def unlock_user(
435+
self,
436+
server,
437+
domain,
438+
port,
439+
login_user,
440+
password,
441+
base_dn,
442+
use_ssl,
443+
samaccountname,
444+
search_base,
445+
):
388446
if search_base:
389447
base_dn = search_base
390448

@@ -393,91 +451,136 @@ def unlock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,sama
393451
c.search(base_dn, f"(SAMAccountName={samaccountname})")
394452

395453
if len(c.entries) == 0:
396-
return {"success":"false","message":f"User {samaccountname} not found"}
454+
return {"success": "false", "message": f"User {samaccountname} not found"}
397455

398456
user_dn = c.entries[0].entry_dn
399457

400-
c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[0])]})
458+
c.modify(user_dn, {"userAccountControl": [(MODIFY_REPLACE, [0])]})
401459

402460
result = c.result
403461
result["success"] = True
404462

405463
return result
406-
407-
def change_user_password_at_next_login(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base,new_user_password,repeat_new_user_password):
408-
464+
465+
def change_user_password_at_next_login(
466+
self,
467+
server,
468+
domain,
469+
port,
470+
login_user,
471+
password,
472+
base_dn,
473+
use_ssl,
474+
samaccountname,
475+
search_base,
476+
new_user_password,
477+
repeat_new_user_password,
478+
):
409479
if search_base:
410480
base_dn = search_base
411481

412482
if str(new_user_password) != str(repeat_new_user_password):
413-
return {"success":"false","message":"new_user_password and repeat_new_user_password does not match."}
483+
return {
484+
"success": "false",
485+
"message": "new_user_password and repeat_new_user_password does not match.",
486+
}
414487

415488
c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl)
416489

417490
c.search(base_dn, f"(SAMAccountName={samaccountname})")
418491

419492
if len(c.entries) == 0:
420-
return {"success":"false","message":f"User {samaccountname} not found"}
493+
return {"success": "false", "message": f"User {samaccountname} not found"}
421494

422495
user_dn = c.entries[0].entry_dn
423496

424-
c.modify(user_dn, {'pwdLastSet':(MODIFY_REPLACE, [0])})
425-
c.extend.microsoft.modify_password(user_dn, new_user_password.encode('utf-16-le'))
497+
c.modify(user_dn, {"pwdLastSet": (MODIFY_REPLACE, [0])})
498+
c.extend.microsoft.modify_password(
499+
user_dn, new_user_password.encode("utf-16-le")
500+
)
426501

427502
result = c.result
428503
result["success"] = True
429504

430505
return result
431506

432-
def add_user_to_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name):
433-
507+
def add_user_to_group(
508+
self,
509+
server,
510+
domain,
511+
port,
512+
login_user,
513+
password,
514+
base_dn,
515+
use_ssl,
516+
samaccountname,
517+
search_base,
518+
group_name,
519+
):
434520
if search_base:
435521
base_dn = search_base
436522

437523
c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl)
438524

439525
c.search(base_dn, f"(SAMAccountName={samaccountname})")
440526
if len(c.entries) == 0:
441-
return {"success":"false","message":f"User {samaccountname} not found"}
527+
return {"success": "false", "message": f"User {samaccountname} not found"}
442528
user_dn = c.entries[0].entry_dn
443529

444-
search_filter = f'(&(objectClass=group)(cn={group_name}))'
530+
search_filter = f"(&(objectClass=group)(cn={group_name}))"
445531
c.search(base_dn, search_filter, attributes=["distinguishedName"])
446532
if len(c.entries) == 0:
447-
return {"success":"false","message":f"Group {group_name} not found"}
533+
return {"success": "false", "message": f"Group {group_name} not found"}
448534
group_dn = c.entries[0]["distinguishedName"]
449535
print(group_dn)
450536

451-
res = addUsersInGroups(c, user_dn, str(group_dn),fix=True)
537+
res = addUsersInGroups(c, user_dn, str(group_dn), fix=True)
452538
if res == True:
453-
return {"success":"true","message":f"User {samaccountname} was added to group {group_name}"}
539+
return {
540+
"success": "true",
541+
"message": f"User {samaccountname} was added to group {group_name}",
542+
}
454543
else:
455-
return {"success":"false","message":f"Could not add user to group"}
544+
return {"success": "false", "message": f"Could not add user to group"}
456545

457-
def remove_user_from_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name):
458-
546+
def remove_user_from_group(
547+
self,
548+
server,
549+
domain,
550+
port,
551+
login_user,
552+
password,
553+
base_dn,
554+
use_ssl,
555+
samaccountname,
556+
search_base,
557+
group_name,
558+
):
459559
if search_base:
460560
base_dn = search_base
461561

462562
c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl)
463563

464564
c.search(base_dn, f"(SAMAccountName={samaccountname})")
465565
if len(c.entries) == 0:
466-
return {"success":"false","message":f"User {samaccountname} not found"}
566+
return {"success": "false", "message": f"User {samaccountname} not found"}
467567

468568
user_dn = c.entries[0].entry_dn
469-
search_filter = f'(&(objectClass=group)(cn={group_name}))'
569+
search_filter = f"(&(objectClass=group)(cn={group_name}))"
470570
c.search(base_dn, search_filter, attributes=["distinguishedName"])
471571
if len(c.entries) == 0:
472-
return {"success":"false","message":f"Group {group_name} not found"}
572+
return {"success": "false", "message": f"Group {group_name} not found"}
473573

474574
group_dn = c.entries[0]["distinguishedName"]
475575
print(group_dn)
476-
res = removeUsersFromGroups(c, user_dn, str(group_dn),fix=True)
576+
res = removeUsersFromGroups(c, user_dn, str(group_dn), fix=True)
477577
if res == True:
478-
return {"success":"true","message":f"User {samaccountname} was removed from group {group_name}"}
578+
return {
579+
"success": "true",
580+
"message": f"User {samaccountname} was removed from group {group_name}",
581+
}
479582
else:
480-
return {"success":"false","message":f"Could not remove user to group"}
583+
return {"success": "false", "message": f"Could not remove user to group"}
481584

482585

483586
if __name__ == "__main__":

0 commit comments

Comments
 (0)