python-win-ad 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyad/__init__.py +4 -0
- pyad/adbase.py +6 -3
- pyad/adcontainer.py +132 -17
- pyad/adobject.py +35 -2
- pyad/pyadexceptions.py +8 -4
- pyad/pyadutils.py +85 -14
- {python_win_ad-0.6.1.dist-info → python_win_ad-0.6.3.dist-info}/METADATA +82 -83
- python_win_ad-0.6.3.dist-info/RECORD +19 -0
- {python_win_ad-0.6.1.dist-info → python_win_ad-0.6.3.dist-info}/WHEEL +1 -1
- python_win_ad-0.6.1.dist-info/RECORD +0 -19
- {python_win_ad-0.6.1.dist-info → python_win_ad-0.6.3.dist-info}/LICENSE +0 -0
- {python_win_ad-0.6.1.dist-info → python_win_ad-0.6.3.dist-info}/top_level.txt +0 -0
pyad/__init__.py
CHANGED
pyad/adbase.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import sys
|
|
2
3
|
import win32com.client
|
|
3
4
|
|
|
4
5
|
from .pyadexceptions import SetupError
|
|
5
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
6
8
|
_adsi_provider = win32com.client.Dispatch("ADsNameSpaces")
|
|
7
9
|
|
|
8
10
|
try:
|
|
9
11
|
# Discover default domain and forest information
|
|
10
12
|
__default_domain_obj = _adsi_provider.GetObject("", "LDAP://rootDSE")
|
|
11
13
|
except:
|
|
12
|
-
# If there was an error, this
|
|
13
|
-
|
|
14
|
-
"
|
|
14
|
+
# If there was an error, this computer might not be on a domain.
|
|
15
|
+
logger.info(
|
|
16
|
+
"Unable to connect to default domain. "
|
|
17
|
+
"Computer is likely not attached to an AD domain."
|
|
15
18
|
)
|
|
16
19
|
__default_domain_obj = None
|
|
17
20
|
_default_detected_forest = None
|
pyad/adcontainer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pywintypes
|
|
2
2
|
|
|
3
|
+
from typing import Any, Dict, List
|
|
3
4
|
from .adobject import ADObject
|
|
4
5
|
from .aduser import ADUser
|
|
5
6
|
from .adcomputer import ADComputer
|
|
@@ -9,7 +10,21 @@ from . import pyadutils
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ADContainer(ADObject):
|
|
12
|
-
def get_children_iter(
|
|
13
|
+
def get_children_iter(
|
|
14
|
+
self, recursive: bool = False, filter: List[ADObject] = None
|
|
15
|
+
) -> ADObject:
|
|
16
|
+
"""
|
|
17
|
+
Iterate over the children objects in the container.
|
|
18
|
+
|
|
19
|
+
:param recursive: enumerate all containers with in the object,
|
|
20
|
+
defaults to False
|
|
21
|
+
:type recursive: bool, optional
|
|
22
|
+
:param filter: filter to only specific object classes ie ADUser,
|
|
23
|
+
defaults to None
|
|
24
|
+
:type filter: List[ADObject], optional
|
|
25
|
+
:yield: _description_
|
|
26
|
+
:rtype: _type_
|
|
27
|
+
"""
|
|
13
28
|
for com_object in self._ldap_adsi_obj:
|
|
14
29
|
q = ADObject.from_com_object(com_object)
|
|
15
30
|
q.adjust_pyad_type()
|
|
@@ -20,28 +35,59 @@ class ADContainer(ADObject):
|
|
|
20
35
|
if not filter or q.__class__ in filter:
|
|
21
36
|
yield q
|
|
22
37
|
|
|
23
|
-
def get_children(
|
|
38
|
+
def get_children(
|
|
39
|
+
self, recursive: bool = False, filter: List[ADObject] = None
|
|
40
|
+
) -> list:
|
|
24
41
|
"""
|
|
25
|
-
|
|
42
|
+
returns a list of child containers with in the current container. Optionally
|
|
43
|
+
the list can be filtered to specific object classes and search through child
|
|
44
|
+
containers recursively.
|
|
26
45
|
|
|
27
46
|
:param recursive: include children from sub-containers, defaults to False
|
|
28
47
|
:type recursive: bool, optional
|
|
29
48
|
:param filter: filter to only specific object classes ie ADUser, defaults to None
|
|
30
|
-
:type filter:
|
|
49
|
+
:type filter: List[ADObject], optional
|
|
31
50
|
:return: a list of all child objects
|
|
32
51
|
:rtype: list
|
|
33
52
|
"""
|
|
53
|
+
|
|
34
54
|
return list(self.get_children_iter(recursive=recursive, filter=filter))
|
|
35
55
|
|
|
36
|
-
def __create_object(self, type_, name):
|
|
56
|
+
def __create_object(self, type_: str, name: str) -> ADObject:
|
|
37
57
|
prefix = "ou" if type_ == "organizationalUnit" else "cn"
|
|
38
58
|
prefixed_name = "=".join((prefix, name))
|
|
39
59
|
return self._ldap_adsi_obj.Create(type_, prefixed_name)
|
|
40
60
|
|
|
41
61
|
def create_user(
|
|
42
|
-
self,
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
self,
|
|
63
|
+
name: str,
|
|
64
|
+
password: str = None,
|
|
65
|
+
upn_suffix: str = None,
|
|
66
|
+
enable: bool = True,
|
|
67
|
+
optional_attributes: dict = {},
|
|
68
|
+
) -> ADUser:
|
|
69
|
+
"""
|
|
70
|
+
Create a new user object in the container.
|
|
71
|
+
|
|
72
|
+
:param name: The name of the user
|
|
73
|
+
:type name: str
|
|
74
|
+
:param password: The password for the user it is strongly recommended to
|
|
75
|
+
populate this parameter as the account will be created with password
|
|
76
|
+
not required which will not get clear within AD leading to a security
|
|
77
|
+
vulnerability.
|
|
78
|
+
:type password: str, optional
|
|
79
|
+
:param upn_suffix: The upn suffix for the user, defaults to default upn
|
|
80
|
+
suffix for the domain.
|
|
81
|
+
:type upn_suffix: str, optional
|
|
82
|
+
:param enable: Whether the user should be enabled or disabled, defaults to True
|
|
83
|
+
:type enable: bool, optional
|
|
84
|
+
:param optional_attributes: List of additional attribute to set, defaults to {}
|
|
85
|
+
:type optional_attributes: dict, optional
|
|
86
|
+
:return: the created user object
|
|
87
|
+
:rtype: ADUser
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
pyadobj = None
|
|
45
91
|
|
|
46
92
|
try:
|
|
47
93
|
if not upn_suffix:
|
|
@@ -52,21 +98,49 @@ class ADContainer(ADObject):
|
|
|
52
98
|
obj.Put("userPrincipalName", upn)
|
|
53
99
|
obj.SetInfo()
|
|
54
100
|
pyadobj = ADUser.from_com_object(obj)
|
|
101
|
+
|
|
55
102
|
if enable:
|
|
56
103
|
pyadobj.enable()
|
|
104
|
+
|
|
57
105
|
if password:
|
|
58
106
|
pyadobj.set_password(password)
|
|
107
|
+
pyadobj.set_user_account_control_setting("PASSWD_NOTREQD", False)
|
|
108
|
+
|
|
59
109
|
pyadobj.set_user_account_control_setting("NORMAL_ACCOUNT", True)
|
|
60
|
-
pyadobj.set_user_account_control_setting("PASSWD_NOTREQD", False)
|
|
61
110
|
pyadobj.update_attributes(optional_attributes)
|
|
62
111
|
return pyadobj
|
|
63
112
|
except pywintypes.com_error as e:
|
|
113
|
+
if pyadobj:
|
|
114
|
+
# clean up the object if it was created
|
|
115
|
+
pyadobj.delete()
|
|
64
116
|
pyadutils.pass_up_com_exception(e)
|
|
65
117
|
|
|
66
118
|
def create_group(
|
|
67
|
-
self,
|
|
68
|
-
|
|
69
|
-
|
|
119
|
+
self,
|
|
120
|
+
name: str,
|
|
121
|
+
security_enabled: bool = True,
|
|
122
|
+
scope: str = "GLOBAL",
|
|
123
|
+
optional_attributes: Dict[str, Any] = {},
|
|
124
|
+
) -> ADGroup:
|
|
125
|
+
"""
|
|
126
|
+
Create a new group object in the container
|
|
127
|
+
|
|
128
|
+
:param name: The Group Name
|
|
129
|
+
:type name: str
|
|
130
|
+
:param security_enabled: If this is a security enabled group or a distribution
|
|
131
|
+
group, defaults to True
|
|
132
|
+
:type security_enabled: bool, optional
|
|
133
|
+
:param scope: The scope of group. Must be one of [GLOBAL, LOCAL, UNIVERSAL].
|
|
134
|
+
Defaults to "GLOBAL"
|
|
135
|
+
:type scope: str, optional
|
|
136
|
+
:param optional_attributes: Additional attributes to set when creating the
|
|
137
|
+
object, defaults to {}
|
|
138
|
+
:type optional_attributes: Dict[str,Any], optional
|
|
139
|
+
:return: The created group object
|
|
140
|
+
:rtype: ADGroup
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
obj = None
|
|
70
144
|
|
|
71
145
|
try:
|
|
72
146
|
obj = self.__create_object("group", name)
|
|
@@ -80,10 +154,30 @@ class ADContainer(ADObject):
|
|
|
80
154
|
pyadobj.update_attributes(optional_attributes)
|
|
81
155
|
return pyadobj
|
|
82
156
|
except pywintypes.com_error as e:
|
|
157
|
+
if obj:
|
|
158
|
+
obj.delete()
|
|
83
159
|
pyadutils.pass_up_com_exception(e)
|
|
160
|
+
except KeyError:
|
|
161
|
+
if obj:
|
|
162
|
+
obj.delete()
|
|
163
|
+
raise ValueError(f"Invalid scope: {scope}")
|
|
164
|
+
|
|
165
|
+
def create_container(
|
|
166
|
+
self, name: str, optional_attributes: Dict[str, Any] = {}
|
|
167
|
+
) -> "ADContainer":
|
|
168
|
+
"""
|
|
169
|
+
Create a new organizational unit in the container
|
|
170
|
+
|
|
171
|
+
:param name: The name of the container
|
|
172
|
+
:type name: str
|
|
173
|
+
:param optional_attributes: Additional attributes to set when creating the
|
|
174
|
+
object, defaults to {}
|
|
175
|
+
:type optional_attributes: Dict[str,Any], optional
|
|
176
|
+
:return: the created container object
|
|
177
|
+
:rtype: ADContainer
|
|
178
|
+
"""
|
|
84
179
|
|
|
85
|
-
|
|
86
|
-
"""Create a new organizational unit in the container"""
|
|
180
|
+
obj = None
|
|
87
181
|
|
|
88
182
|
try:
|
|
89
183
|
obj = self.__create_object("organizationalUnit", name)
|
|
@@ -92,10 +186,29 @@ class ADContainer(ADObject):
|
|
|
92
186
|
pyadobj.update_attributes(optional_attributes)
|
|
93
187
|
return pyadobj
|
|
94
188
|
except pywintypes.com_error as e:
|
|
189
|
+
if obj:
|
|
190
|
+
obj.delete()
|
|
95
191
|
pyadutils.pass_up_com_exception(e)
|
|
96
192
|
|
|
97
|
-
def create_computer(
|
|
98
|
-
|
|
193
|
+
def create_computer(
|
|
194
|
+
self, name: str, enable: bool = True, optional_attributes: Dict[str, Any] = {}
|
|
195
|
+
) -> ADComputer:
|
|
196
|
+
"""
|
|
197
|
+
Create a new computer object in the container
|
|
198
|
+
|
|
199
|
+
:param name: The computer name
|
|
200
|
+
:type name: str
|
|
201
|
+
:param enable: Whether the computer should be enabled or disabled,
|
|
202
|
+
defaults to True
|
|
203
|
+
:type enable: bool, optional
|
|
204
|
+
:param optional_attributes: Additional attributes to set when creating the
|
|
205
|
+
object, defaults to {}
|
|
206
|
+
:type optional_attributes: Dict[str,Any], optional
|
|
207
|
+
:return: the created computer object
|
|
208
|
+
:rtype: ADComputer
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
obj = None
|
|
99
212
|
|
|
100
213
|
try:
|
|
101
214
|
obj = self.__create_object("computer", name)
|
|
@@ -111,9 +224,11 @@ class ADContainer(ADObject):
|
|
|
111
224
|
pyadobj.update_attributes(optional_attributes)
|
|
112
225
|
return pyadobj
|
|
113
226
|
except pywintypes.com_error as e:
|
|
227
|
+
if obj:
|
|
228
|
+
obj.delete()
|
|
114
229
|
pyadutils.pass_up_com_exception(e)
|
|
115
230
|
|
|
116
|
-
def remove_child(self, child) -> None:
|
|
231
|
+
def remove_child(self, child: ADObject) -> None:
|
|
117
232
|
"""Removes the child object from the domain"""
|
|
118
233
|
|
|
119
234
|
self._ldap_adsi_obj.Delete(child.type, child.prefixed_cn)
|
pyad/adobject.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import win32com
|
|
2
3
|
import pywintypes
|
|
3
4
|
import time
|
|
@@ -16,6 +17,7 @@ from .pyadconstants import (
|
|
|
16
17
|
ADS_USER_FLAG,
|
|
17
18
|
)
|
|
18
19
|
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
@total_ordering
|
|
21
23
|
class ADObject(ADBase):
|
|
@@ -231,6 +233,37 @@ class ADObject(ADBase):
|
|
|
231
233
|
else:
|
|
232
234
|
raise AttributeError(attribute)
|
|
233
235
|
|
|
236
|
+
def __getitem__(self, key: str):
|
|
237
|
+
"""
|
|
238
|
+
Get an attribute from the ADObject attributes.
|
|
239
|
+
|
|
240
|
+
:param key: The attribute name to retrieve
|
|
241
|
+
:type key: str
|
|
242
|
+
:raises AttributeError: if the key does not exist
|
|
243
|
+
:return: The value of the attribute
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
if hasattr(self._ldap_adsi_obj, key):
|
|
247
|
+
return self.get_attribute(key, False)
|
|
248
|
+
else:
|
|
249
|
+
raise AttributeError(key)
|
|
250
|
+
|
|
251
|
+
def __setitem__(self, key: str, value):
|
|
252
|
+
"""
|
|
253
|
+
Set an attribute on the ADObject.
|
|
254
|
+
|
|
255
|
+
:param key: The attribute name to set
|
|
256
|
+
:type key: str
|
|
257
|
+
:param value: The value to set the attribute to
|
|
258
|
+
:type value: Any
|
|
259
|
+
:raises AttributeError: if the key does not exist
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
if hasattr(self._ldap_adsi_obj, key):
|
|
263
|
+
self.update_attribute(key, value)
|
|
264
|
+
else:
|
|
265
|
+
raise AttributeError(key)
|
|
266
|
+
|
|
234
267
|
def _flush(self):
|
|
235
268
|
"""Commits any changes to the AD object."""
|
|
236
269
|
self._ldap_adsi_obj.SetInfo()
|
|
@@ -633,7 +666,7 @@ class ADObject(ADBase):
|
|
|
633
666
|
)
|
|
634
667
|
node.appendChild(text)
|
|
635
668
|
except:
|
|
636
|
-
|
|
669
|
+
logger.error("attribute: %s not xml-able" % attribute)
|
|
637
670
|
else:
|
|
638
671
|
node.setAttribute("type", "multiValued")
|
|
639
672
|
ok_elem = False
|
|
@@ -653,7 +686,7 @@ class ADObject(ADBase):
|
|
|
653
686
|
node.appendChild(valnode)
|
|
654
687
|
ok_elem = True
|
|
655
688
|
except:
|
|
656
|
-
|
|
689
|
+
logger.error("attribute: %s not xml-able" % attribute)
|
|
657
690
|
if ok_elem:
|
|
658
691
|
adobj_xml_doc.appendChild(node)
|
|
659
692
|
return doc.toxml(encoding="UTF-8")
|
pyad/pyadexceptions.py
CHANGED
|
@@ -46,14 +46,18 @@ class InvalidObjectException(noObjectFoundException, win32Exception):
|
|
|
46
46
|
class InvalidAttribute(BaseException, AttributeError):
|
|
47
47
|
def __str__(self):
|
|
48
48
|
return (
|
|
49
|
-
'The attribute "%s" is not permitted by the schema definition of the
|
|
49
|
+
'The attribute "%s" is not permitted by the schema definition of the '
|
|
50
|
+
'object "%s" (the requested attribute does not exist).'
|
|
50
51
|
% (self.attribute, self.obj)
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class noExecutedQuery(BaseException):
|
|
55
56
|
def __str__(self):
|
|
56
|
-
return
|
|
57
|
+
return (
|
|
58
|
+
"No query has been executed. Therefore there are no results to return. "
|
|
59
|
+
"Execute a query before requesting results."
|
|
60
|
+
)
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
class invalidResults(BaseException):
|
|
@@ -62,8 +66,8 @@ class invalidResults(BaseException):
|
|
|
62
66
|
|
|
63
67
|
def __str__(self):
|
|
64
68
|
return (
|
|
65
|
-
"The specified query returned %i results. getSingleResults only functions
|
|
66
|
-
% self.number_results
|
|
69
|
+
"The specified query returned %i results. getSingleResults only functions "
|
|
70
|
+
"with a single result." % self.number_results
|
|
67
71
|
)
|
|
68
72
|
|
|
69
73
|
|
pyad/pyadutils.py
CHANGED
|
@@ -41,17 +41,34 @@ def validate_credentials(
|
|
|
41
41
|
return None
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def convert_error_code(error_code):
|
|
44
|
+
def convert_error_code(error_code: int) -> int:
|
|
45
45
|
"""
|
|
46
46
|
Convert error code from the format returned by pywin32 to the format that Microsoft
|
|
47
47
|
documents everything in.
|
|
48
|
+
|
|
49
|
+
:param error_code: error code
|
|
50
|
+
:type error_code: int
|
|
51
|
+
:return: The error code in the format Microsoft documents it
|
|
52
|
+
:rtype: int
|
|
48
53
|
"""
|
|
49
54
|
|
|
50
55
|
return error_code % 2**32
|
|
51
56
|
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
def interpret_com_exception(
|
|
59
|
+
excp: "pywintype.com_error", additional_info: dict = {}
|
|
60
|
+
) -> dict:
|
|
61
|
+
"""
|
|
62
|
+
Convert a pywin32 com_error exception into a dictionary of error information.
|
|
63
|
+
|
|
64
|
+
:param excp: pywin32 com_error exception
|
|
65
|
+
:type excp: pywintype.com_error
|
|
66
|
+
:param additional_info: any additional information with the error, defaults to {}
|
|
67
|
+
:type additional_info: dict, optional
|
|
68
|
+
:return: a dictionary of error information
|
|
69
|
+
:rtype: dict
|
|
70
|
+
"""
|
|
71
|
+
|
|
55
72
|
d = {}
|
|
56
73
|
d["error_num"] = convert_error_code(excp.args[2][5])
|
|
57
74
|
# for some reason hex() includes the L for long in the hex...
|
|
@@ -87,7 +104,18 @@ def interpret_com_exception(excp, additional_info={}):
|
|
|
87
104
|
return d
|
|
88
105
|
|
|
89
106
|
|
|
90
|
-
def pass_up_com_exception(excp, additional_info={}):
|
|
107
|
+
def pass_up_com_exception(excp: "pywintype.com_error", additional_info: dict = {}):
|
|
108
|
+
"""
|
|
109
|
+
reparse the com_error into a sane exception and raise it.
|
|
110
|
+
|
|
111
|
+
:param excp: the com_error exception
|
|
112
|
+
:type excp: pywintype.com_error
|
|
113
|
+
:param additional_info: Additional exception details, defaults to {}
|
|
114
|
+
:type additional_info: dict, optional
|
|
115
|
+
:raises excp: if we don't know how to handle the exception raise the original
|
|
116
|
+
exception
|
|
117
|
+
"""
|
|
118
|
+
|
|
91
119
|
if excp.__class__ in (genericADSIException, comException, win32Exception):
|
|
92
120
|
raise excp
|
|
93
121
|
else:
|
|
@@ -110,9 +138,14 @@ def pass_up_com_exception(excp, additional_info={}):
|
|
|
110
138
|
|
|
111
139
|
|
|
112
140
|
def convert_datetime(adsi_time_com_obj):
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
"""
|
|
142
|
+
Converts 64-bit integer COM object representing time into
|
|
143
|
+
a python datetime object.
|
|
144
|
+
|
|
145
|
+
Credit goes to John Nielsen who documented this at
|
|
146
|
+
`<http://docs.activestate.com/activepython/2.6/pywin32/html/com/help/active_directory.html>`_.
|
|
147
|
+
"""
|
|
148
|
+
|
|
116
149
|
if not hasattr(adsi_time_com_obj, "highpart") or not hasattr(
|
|
117
150
|
adsi_time_com_obj, "lowpart"
|
|
118
151
|
):
|
|
@@ -133,19 +166,38 @@ def convert_datetime(adsi_time_com_obj):
|
|
|
133
166
|
return datetime.datetime.fromtimestamp(date_value)
|
|
134
167
|
|
|
135
168
|
|
|
136
|
-
def convert_bigint(obj):
|
|
137
|
-
|
|
169
|
+
def convert_bigint(obj) -> int:
|
|
170
|
+
"""
|
|
171
|
+
Converts a ADSI time object to an integer.
|
|
172
|
+
|
|
173
|
+
based on http://www.selfadsi.org/ads-attributes/user-usnChanged.htm
|
|
174
|
+
|
|
175
|
+
:param obj: the AD bigint object
|
|
176
|
+
:raises AttributeError: invalid object type
|
|
177
|
+
:return: the decimal value of the object
|
|
178
|
+
:rtype: int
|
|
179
|
+
"""
|
|
180
|
+
|
|
138
181
|
if hasattr(obj, "HighPart") and hasattr(obj, "LowPart"):
|
|
139
182
|
h, l = obj.HighPart, obj.LowPart
|
|
140
183
|
if l < 0:
|
|
141
184
|
h += 1
|
|
142
185
|
return (h << 32) + l
|
|
143
186
|
else:
|
|
144
|
-
raise
|
|
187
|
+
raise AttributeError(
|
|
188
|
+
f"Expected adsi time object got '{obj.__class__.__name__}'"
|
|
189
|
+
)
|
|
190
|
+
|
|
145
191
|
|
|
192
|
+
def convert_timespan(obj) -> datetime.timedelta:
|
|
193
|
+
"""
|
|
194
|
+
Converts COM object representing time span to a python time span object.
|
|
195
|
+
|
|
196
|
+
:param obj: ADSI time span object
|
|
197
|
+
:return: the python timedelta object
|
|
198
|
+
:rtype: datetime.timedelta
|
|
199
|
+
"""
|
|
146
200
|
|
|
147
|
-
def convert_timespan(obj):
|
|
148
|
-
"""Converts COM object representing time span to a python time span object."""
|
|
149
201
|
as_seconds = (
|
|
150
202
|
abs(convert_bigint(obj)) / 10000000
|
|
151
203
|
) # number of 100 nanoseconds in a second
|
|
@@ -160,7 +212,17 @@ def convert_sid(sid_object):
|
|
|
160
212
|
return pywintypes.SID(bytes(sid_object))
|
|
161
213
|
|
|
162
214
|
|
|
163
|
-
def generate_list(input):
|
|
215
|
+
def generate_list(input) -> list:
|
|
216
|
+
"""
|
|
217
|
+
converts a set or tuple to a list or returns the input in a list if it is not
|
|
218
|
+
a list.
|
|
219
|
+
|
|
220
|
+
:param input: a list like object or any
|
|
221
|
+
:type input: list, set, tuple, Any
|
|
222
|
+
:return: a list
|
|
223
|
+
:rtype: list
|
|
224
|
+
"""
|
|
225
|
+
|
|
164
226
|
if type(input) is list:
|
|
165
227
|
return input
|
|
166
228
|
elif type(input) in (set, tuple):
|
|
@@ -171,7 +233,16 @@ def generate_list(input):
|
|
|
171
233
|
]
|
|
172
234
|
|
|
173
235
|
|
|
174
|
-
def escape_path(path):
|
|
236
|
+
def escape_path(path: str) -> str:
|
|
237
|
+
"""
|
|
238
|
+
escapes a path for use in ADSI.
|
|
239
|
+
|
|
240
|
+
:param path: the raw path to escape
|
|
241
|
+
:type path: str
|
|
242
|
+
:return: the escaped path
|
|
243
|
+
:rtype: str
|
|
244
|
+
"""
|
|
245
|
+
|
|
175
246
|
escapes = (
|
|
176
247
|
("\+", "+"),
|
|
177
248
|
("\*", "*"),
|
|
@@ -1,83 +1,82 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: python-win-ad
|
|
3
|
-
Version: 0.6.
|
|
4
|
-
Summary: An Object-Oriented Active Directory management framework built on ADSI
|
|
5
|
-
Home-page: https://github.com/jcarswell/pyad/
|
|
6
|
-
Author: Zakir Durumeric
|
|
7
|
-
Author-email: zakird@gmail.com
|
|
8
|
-
Maintainer: Josh Carswell
|
|
9
|
-
Maintainer-email: Josh.Carswell@thecarswells.ca
|
|
10
|
-
License: Apache License, Version 2.0
|
|
11
|
-
Project-URL: Documentation, https://jcarswell.github.io/pyad/
|
|
12
|
-
Project-URL: Issues, https://github.com/jcarswell/pyad/issues/
|
|
13
|
-
Keywords: python microsoft windows active directory AD adsi
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: Intended Audience :: System Administrators
|
|
16
|
-
Classifier: Natural Language :: English
|
|
17
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
-
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
|
|
19
|
-
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
:
|
|
33
|
-
:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
to
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
To
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
and limitations under the License.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-win-ad
|
|
3
|
+
Version: 0.6.3
|
|
4
|
+
Summary: An Object-Oriented Active Directory management framework built on ADSI
|
|
5
|
+
Home-page: https://github.com/jcarswell/pyad/
|
|
6
|
+
Author: Zakir Durumeric
|
|
7
|
+
Author-email: zakird@gmail.com
|
|
8
|
+
Maintainer: Josh Carswell
|
|
9
|
+
Maintainer-email: Josh.Carswell@thecarswells.ca
|
|
10
|
+
License: Apache License, Version 2.0
|
|
11
|
+
Project-URL: Documentation, https://jcarswell.github.io/pyad/
|
|
12
|
+
Project-URL: Issues, https://github.com/jcarswell/pyad/issues/
|
|
13
|
+
Keywords: python microsoft windows active directory AD adsi
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Obsoletes: pyad
|
|
25
|
+
Requires-Python: >=3.6
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: setuptools
|
|
28
|
+
Requires-Dist: pywin32
|
|
29
|
+
|
|
30
|
+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
|
31
|
+
:align: center
|
|
32
|
+
:target: https://github.com/psf/black
|
|
33
|
+
:alt: code style: black
|
|
34
|
+
|
|
35
|
+
Introduction
|
|
36
|
+
------------
|
|
37
|
+
|
|
38
|
+
pyad is a Python library designed to provide a simple, Pythonic interface to Active Directory
|
|
39
|
+
through ADSI on the Windows platform. Complete documentation can be found at
|
|
40
|
+
http://jcarswell.github.io/pyad/. Code is maintained at https://github.com/jcarswell/pyad. The
|
|
41
|
+
library can be downloaded from PyPI at https://github.com/jcarswell/pyad.
|
|
42
|
+
|
|
43
|
+
Breaking Changes from upstream
|
|
44
|
+
------------------------------
|
|
45
|
+
|
|
46
|
+
ADObject:
|
|
47
|
+
|
|
48
|
+
- _get_password_last_set - Act's like AD and returns 1970-01-01 if the date can't be parsed
|
|
49
|
+
- get_last_login - Act's like AD and returns 1970-01-01 if the date can't be parsed
|
|
50
|
+
|
|
51
|
+
Importing pyad directly exposes set_defaults, ADQuery, ADComputer, ADContainer, ADDomain,
|
|
52
|
+
ADGroup, ADUser, from_cn, from_dn, from_guid. Importing pyad.pyad no longer imports
|
|
53
|
+
the sub modules
|
|
54
|
+
|
|
55
|
+
Most ADObject update methods now take flush as an optional argument that defaults to True
|
|
56
|
+
to maintain compatibility with upstream code. For large updates it's recommended to set
|
|
57
|
+
this to False until you are ready to write out the change, otherwise you may run into a
|
|
58
|
+
back-off period in AD where all further changes will fail.
|
|
59
|
+
|
|
60
|
+
Requirements
|
|
61
|
+
------------
|
|
62
|
+
|
|
63
|
+
pyad requires pywin32, available at https://github.com/mhammond/pywin32.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Testing
|
|
67
|
+
-------
|
|
68
|
+
|
|
69
|
+
To run unittest you will need to set the configuration to be specific to your environment.
|
|
70
|
+
To do this you will need to edit config.py located in the tests folder.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
License
|
|
74
|
+
-------
|
|
75
|
+
|
|
76
|
+
pyad is licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy
|
|
77
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
78
|
+
|
|
79
|
+
Unless required by applicable law or agreed to in writing, software distributed under the
|
|
80
|
+
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
81
|
+
either express or implied. See the License for the specific language governing permissions
|
|
82
|
+
and limitations under the License.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
pyad/__init__.py,sha256=RlMEJyo2g6VwxlOhDJzfPfrUGw90SVFZThTN7Gmf5Nk,1771
|
|
2
|
+
pyad/adbase.py,sha256=hghiIpcQjJs0X9laqwbkShjlW21jiKIHJGBw3CDAMN4,3016
|
|
3
|
+
pyad/adcomputer.py,sha256=Wnsx-aD0ugOsgUyHx__6GWd2ogk3aiv4ARqhI_AoPZk,1109
|
|
4
|
+
pyad/adcontainer.py,sha256=dod_T6o7k-2cUmSVragtJnQrlaNwe7Mx5sNCexxbrjQ,8817
|
|
5
|
+
pyad/addomain.py,sha256=ppCUyONuNJAFVPxxCl4Z2o1-Lp0XesS3Wo1ao0YEoDg,850
|
|
6
|
+
pyad/adgroup.py,sha256=nevw3ZBAw58o_qYoXn7dN1bnOvIuwfsZMbZH6cF_dlM,11377
|
|
7
|
+
pyad/adobject.py,sha256=7_497WoGXjdvVGfm5SuszFXEH5rK4R6E7WFFdVM8Rc8,30791
|
|
8
|
+
pyad/adquery.py,sha256=tv0dKkBgcSnhU7Garv6jAEGiUJkwC256I3o2jUpCS7M,10820
|
|
9
|
+
pyad/adsearch.py,sha256=a24vVolTRINQpmuQ3Ldq_2hlHXl9r700vjilINqXk8M,2702
|
|
10
|
+
pyad/aduser.py,sha256=DK0ADC0qXLUodTW0cphvgg8-0iSewaoYw5rSTtje6Dg,3936
|
|
11
|
+
pyad/pyad.py,sha256=moPUvolc0vI-JIIIjW8otUclgkUov0fT1pym1vufdNs,1091
|
|
12
|
+
pyad/pyadconstants.py,sha256=wny29f6aAYhRosWp-lXp_2Rn8HCn3J0BI_G1XFMg5DA,6739
|
|
13
|
+
pyad/pyadexceptions.py,sha256=R8q5dSsqOgULpqVYoz2yvNi6lENZ9wg0PZQnp2Y1sWg,2149
|
|
14
|
+
pyad/pyadutils.py,sha256=0c3wTZEUCv3WE59SO3bzJYfhaO10HsCbQS1D3lo9X10,10486
|
|
15
|
+
python_win_ad-0.6.3.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
16
|
+
python_win_ad-0.6.3.dist-info/METADATA,sha256=Hv4j9QaJBQdPgxPcdNcRv6zYqYfCk2jDQxDt6nATpXo,3346
|
|
17
|
+
python_win_ad-0.6.3.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
18
|
+
python_win_ad-0.6.3.dist-info/top_level.txt,sha256=nRuluAlQ0ORm6J1nBi_n7OlMbbqTp7JDtl3g14wpSmI,5
|
|
19
|
+
python_win_ad-0.6.3.dist-info/RECORD,,
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
pyad/__init__.py,sha256=y_NCgMefngD034sVrtAC--JMW1pXZ7_2Glwp9-4nqCw,1691
|
|
2
|
-
pyad/adbase.py,sha256=cdFIadGftHJYZHH1_Byz6h1JbNgRihy4CqqH_Na1Ak4,2954
|
|
3
|
-
pyad/adcomputer.py,sha256=Wnsx-aD0ugOsgUyHx__6GWd2ogk3aiv4ARqhI_AoPZk,1109
|
|
4
|
-
pyad/adcontainer.py,sha256=KXQyQFYMggPt8lyYftZQit1WCnr4bdc5eBIq-y3IzDc,4867
|
|
5
|
-
pyad/addomain.py,sha256=ppCUyONuNJAFVPxxCl4Z2o1-Lp0XesS3Wo1ao0YEoDg,850
|
|
6
|
-
pyad/adgroup.py,sha256=nevw3ZBAw58o_qYoXn7dN1bnOvIuwfsZMbZH6cF_dlM,11377
|
|
7
|
-
pyad/adobject.py,sha256=V1Vt4eCK5DY0bnI3m_-I_FJn_3geZ_5rrlklznLF0eg,29784
|
|
8
|
-
pyad/adquery.py,sha256=tv0dKkBgcSnhU7Garv6jAEGiUJkwC256I3o2jUpCS7M,10820
|
|
9
|
-
pyad/adsearch.py,sha256=a24vVolTRINQpmuQ3Ldq_2hlHXl9r700vjilINqXk8M,2702
|
|
10
|
-
pyad/aduser.py,sha256=DK0ADC0qXLUodTW0cphvgg8-0iSewaoYw5rSTtje6Dg,3936
|
|
11
|
-
pyad/pyad.py,sha256=moPUvolc0vI-JIIIjW8otUclgkUov0fT1pym1vufdNs,1091
|
|
12
|
-
pyad/pyadconstants.py,sha256=wny29f6aAYhRosWp-lXp_2Rn8HCn3J0BI_G1XFMg5DA,6739
|
|
13
|
-
pyad/pyadexceptions.py,sha256=9spQLlnQ1R0Pp9W93DoZTEM_aim406SA6g7SEAYJLdA,2088
|
|
14
|
-
pyad/pyadutils.py,sha256=Hx0vYwjBZSuLAjlhu9dXgv4H5k68G-EKsYkWHNZ41JU,8695
|
|
15
|
-
python_win_ad-0.6.1.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
16
|
-
python_win_ad-0.6.1.dist-info/METADATA,sha256=eCN0uVdn-MO0JtYu2Uk-a1EKLJy5tmHrzfk2SRglo4M,3313
|
|
17
|
-
python_win_ad-0.6.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
18
|
-
python_win_ad-0.6.1.dist-info/top_level.txt,sha256=nRuluAlQ0ORm6J1nBi_n7OlMbbqTp7JDtl3g14wpSmI,5
|
|
19
|
-
python_win_ad-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|