python-win-ad 0.6.1__tar.gz → 0.6.3__tar.gz

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.
Files changed (27) hide show
  1. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/PKG-INFO +4 -3
  2. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/__init__.py +4 -0
  3. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adbase.py +6 -3
  4. python_win_ad-0.6.3/pyad/adcontainer.py +238 -0
  5. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adobject.py +35 -2
  6. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/pyadexceptions.py +8 -4
  7. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/pyadutils.py +85 -14
  8. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/python_win_ad.egg-info/PKG-INFO +4 -3
  9. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/python_win_ad.egg-info/SOURCES.txt +3 -1
  10. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/setup.py +2 -3
  11. python_win_ad-0.6.3/tests/tests_adbase.py +22 -0
  12. python_win_ad-0.6.3/tests/tests_adquery.py +53 -0
  13. python-win-ad-0.6.1/pyad/adcontainer.py +0 -123
  14. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/LICENSE +0 -0
  15. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/README.rst +0 -0
  16. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adcomputer.py +0 -0
  17. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/addomain.py +0 -0
  18. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adgroup.py +0 -0
  19. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adquery.py +0 -0
  20. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/adsearch.py +0 -0
  21. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/aduser.py +0 -0
  22. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/pyad.py +0 -0
  23. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/pyad/pyadconstants.py +0 -0
  24. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/python_win_ad.egg-info/dependency_links.txt +0 -0
  25. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/python_win_ad.egg-info/requires.txt +0 -0
  26. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/python_win_ad.egg-info/top_level.txt +0 -0
  27. {python-win-ad-0.6.1 → python_win_ad-0.6.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-win-ad
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: An Object-Oriented Active Directory management framework built on ADSI
5
5
  Home-page: https://github.com/jcarswell/pyad/
6
6
  Author: Zakir Durumeric
@@ -17,14 +17,15 @@ Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: Microsoft :: Windows
18
18
  Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.6
21
- Classifier: Programming Language :: Python :: 3.7
22
20
  Classifier: Programming Language :: Python :: 3.8
23
21
  Classifier: Programming Language :: Python :: 3.9
24
22
  Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
25
24
  Obsoletes: pyad
26
25
  Requires-Python: >=3.6
27
26
  License-File: LICENSE
27
+ Requires-Dist: setuptools
28
+ Requires-Dist: pywin32
28
29
 
29
30
  .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
30
31
  :align: center
@@ -1,3 +1,7 @@
1
+ # package logger
2
+ import logging
3
+ logging.basicConfig(level=logging.WARNING)
4
+
1
5
  __all__ = [
2
6
  "set_defaults",
3
7
  "ADQuery",
@@ -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 this computer might not be on a domain.
13
- print(
14
- "WARN: unable to connect to default domain. Computer is likely not attached to an AD domain"
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
@@ -0,0 +1,238 @@
1
+ import pywintypes
2
+
3
+ from typing import Any, Dict, List
4
+ from .adobject import ADObject
5
+ from .aduser import ADUser
6
+ from .adcomputer import ADComputer
7
+ from .adgroup import ADGroup
8
+ from . import pyadconstants
9
+ from . import pyadutils
10
+
11
+
12
+ class ADContainer(ADObject):
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
+ """
28
+ for com_object in self._ldap_adsi_obj:
29
+ q = ADObject.from_com_object(com_object)
30
+ q.adjust_pyad_type()
31
+ if q.type == "organizationalUnit" and recursive:
32
+ for c in q.get_children_iter(recursive=recursive):
33
+ if not filter or c.__class__ in filter:
34
+ yield c
35
+ if not filter or q.__class__ in filter:
36
+ yield q
37
+
38
+ def get_children(
39
+ self, recursive: bool = False, filter: List[ADObject] = None
40
+ ) -> list:
41
+ """
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.
45
+
46
+ :param recursive: include children from sub-containers, defaults to False
47
+ :type recursive: bool, optional
48
+ :param filter: filter to only specific object classes ie ADUser, defaults to None
49
+ :type filter: List[ADObject], optional
50
+ :return: a list of all child objects
51
+ :rtype: list
52
+ """
53
+
54
+ return list(self.get_children_iter(recursive=recursive, filter=filter))
55
+
56
+ def __create_object(self, type_: str, name: str) -> ADObject:
57
+ prefix = "ou" if type_ == "organizationalUnit" else "cn"
58
+ prefixed_name = "=".join((prefix, name))
59
+ return self._ldap_adsi_obj.Create(type_, prefixed_name)
60
+
61
+ def create_user(
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
91
+
92
+ try:
93
+ if not upn_suffix:
94
+ upn_suffix = self.get_domain().get_default_upn()
95
+ upn = "@".join((name, upn_suffix))
96
+ obj = self.__create_object("user", name)
97
+ obj.Put("sAMAccountName", optional_attributes.get("sAMAccountName", name))
98
+ obj.Put("userPrincipalName", upn)
99
+ obj.SetInfo()
100
+ pyadobj = ADUser.from_com_object(obj)
101
+
102
+ if enable:
103
+ pyadobj.enable()
104
+
105
+ if password:
106
+ pyadobj.set_password(password)
107
+ pyadobj.set_user_account_control_setting("PASSWD_NOTREQD", False)
108
+
109
+ pyadobj.set_user_account_control_setting("NORMAL_ACCOUNT", True)
110
+ pyadobj.update_attributes(optional_attributes)
111
+ return pyadobj
112
+ except pywintypes.com_error as e:
113
+ if pyadobj:
114
+ # clean up the object if it was created
115
+ pyadobj.delete()
116
+ pyadutils.pass_up_com_exception(e)
117
+
118
+ def create_group(
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
144
+
145
+ try:
146
+ obj = self.__create_object("group", name)
147
+ obj.Put("sAMAccountName", name)
148
+ val = pyadconstants.ADS_GROUP_TYPE[scope]
149
+ if security_enabled:
150
+ val = val | pyadconstants.ADS_GROUP_TYPE["SECURITY_ENABLED"]
151
+ obj.Put("groupType", val)
152
+ obj.SetInfo()
153
+ pyadobj = ADGroup.from_com_object(obj)
154
+ pyadobj.update_attributes(optional_attributes)
155
+ return pyadobj
156
+ except pywintypes.com_error as e:
157
+ if obj:
158
+ obj.delete()
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
+ """
179
+
180
+ obj = None
181
+
182
+ try:
183
+ obj = self.__create_object("organizationalUnit", name)
184
+ obj.SetInfo()
185
+ pyadobj = ADContainer.from_com_object(obj)
186
+ pyadobj.update_attributes(optional_attributes)
187
+ return pyadobj
188
+ except pywintypes.com_error as e:
189
+ if obj:
190
+ obj.delete()
191
+ pyadutils.pass_up_com_exception(e)
192
+
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
212
+
213
+ try:
214
+ obj = self.__create_object("computer", name)
215
+ obj.Put("sAMAccountName", name + "$")
216
+ if enable:
217
+ obj.Put("userAccountControl", 4128)
218
+ else:
219
+ obj.Put("userAccountControl", 4130)
220
+ obj.SetInfo()
221
+ pyadobj = ADComputer.from_com_object(obj)
222
+ if enable:
223
+ pyadobj.enable()
224
+ pyadobj.update_attributes(optional_attributes)
225
+ return pyadobj
226
+ except pywintypes.com_error as e:
227
+ if obj:
228
+ obj.delete()
229
+ pyadutils.pass_up_com_exception(e)
230
+
231
+ def remove_child(self, child: ADObject) -> None:
232
+ """Removes the child object from the domain"""
233
+
234
+ self._ldap_adsi_obj.Delete(child.type, child.prefixed_cn)
235
+
236
+
237
+ ADObject._py_ad_object_mappings["organizationalUnit"] = ADContainer
238
+ ADObject._py_ad_object_mappings["container"] = ADContainer
@@ -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
- print("attribute: %s not xml-able" % attribute)
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
- print("attribute: %s not xml-able" % attribute)
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")
@@ -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 object "%s" (the requested attribute does not exist).'
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 "No query has been executed. Therefore there are no results to return. Execute a query before requesting results."
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 with a single result."
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
 
@@ -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
- # expects the actually pywintypes.com_error exception that's thrown...
54
- def interpret_com_exception(excp, additional_info={}):
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
- """Converts 64-bit integer COM object representing time into a python datetime object."""
114
- # credit goes to John Nielsen who documented this at
115
- # http://docs.activestate.com/activepython/2.6/pywin32/html/com/help/active_directory.html.
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
- # based on http://www.selfadsi.org/ads-attributes/user-usnChanged.htm
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 ValueError(f"Expected adsi time object got '{obj.__class__.__name__}'")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-win-ad
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: An Object-Oriented Active Directory management framework built on ADSI
5
5
  Home-page: https://github.com/jcarswell/pyad/
6
6
  Author: Zakir Durumeric
@@ -17,14 +17,15 @@ Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: Microsoft :: Windows
18
18
  Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.6
21
- Classifier: Programming Language :: Python :: 3.7
22
20
  Classifier: Programming Language :: Python :: 3.8
23
21
  Classifier: Programming Language :: Python :: 3.9
24
22
  Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
25
24
  Obsoletes: pyad
26
25
  Requires-Python: >=3.6
27
26
  License-File: LICENSE
27
+ Requires-Dist: setuptools
28
+ Requires-Dist: pywin32
28
29
 
29
30
  .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
30
31
  :align: center
@@ -19,4 +19,6 @@ python_win_ad.egg-info/PKG-INFO
19
19
  python_win_ad.egg-info/SOURCES.txt
20
20
  python_win_ad.egg-info/dependency_links.txt
21
21
  python_win_ad.egg-info/requires.txt
22
- python_win_ad.egg-info/top_level.txt
22
+ python_win_ad.egg-info/top_level.txt
23
+ tests/tests_adbase.py
24
+ tests/tests_adquery.py
@@ -12,7 +12,7 @@ def read(fname):
12
12
 
13
13
  setup(
14
14
  name="python-win-ad",
15
- version="0.6.1",
15
+ version="0.6.3",
16
16
  author="Zakir Durumeric",
17
17
  author_email="zakird@gmail.com",
18
18
  maintainer="Josh Carswell",
@@ -38,11 +38,10 @@ setup(
38
38
  "Operating System :: Microsoft :: Windows",
39
39
  "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP",
40
40
  "Programming Language :: Python :: 3",
41
- "Programming Language :: Python :: 3.6",
42
- "Programming Language :: Python :: 3.7",
43
41
  "Programming Language :: Python :: 3.8",
44
42
  "Programming Language :: Python :: 3.9",
45
43
  "Programming Language :: Python :: 3.10",
44
+ "Programming Language :: Python :: 3.11",
46
45
  ],
47
46
  install_requires=["setuptools", "pywin32"],
48
47
  )
@@ -0,0 +1,22 @@
1
+ from .pyadunittest import ADTestCase
2
+ from pyad.adbase import ADBase
3
+ import pyad
4
+
5
+ class TestADBase(ADTestCase):
6
+ def setUp(self):
7
+ # set all defaults back to their default
8
+ ADBase.default_ldap_server = None
9
+ ADBase.default_gc_server = None
10
+ ADBase.default_ldap_port = None
11
+ ADBase.default_gc_port = None
12
+
13
+ def test_detected_forest(self):
14
+ self.assertEqual(ADBase.default_domain, self.SANDBOX_DOMAIN)
15
+
16
+ def test_detected_domain(self):
17
+ self.assertEqual(ADBase.default_forest, self.SANDBOX_FOREST)
18
+
19
+ def test_set_defaults(self):
20
+ pyad.set_defaults(ldap_server = self.TEST_DC, ldap_port = 389)
21
+ self.assertEqual(ADBase.default_ldap_server, self.TEST_DC)
22
+ self.assertEqual(ADBase.default_ldap_port, 389)
@@ -0,0 +1,53 @@
1
+ from .pyadunittest import ADTestCase
2
+ import pyad
3
+
4
+ class TestADQuery(ADTestCase):
5
+ def setUp(self):
6
+ self.ad_query = pyad.ADQuery()
7
+
8
+ def test_dne_rowcount(self):
9
+ query = "cn = '%s'" % self.KNOWN_DNE_OBJECT
10
+ self.ad_query.execute_query(where_clause = query)
11
+ self.assertEqual(self.ad_query.row_count, 0)
12
+
13
+ def test_dne_single_result(self):
14
+ query = "cn = '%s'" % self.KNOWN_DNE_OBJECT
15
+ self.ad_query.execute_query(where_clause = query)
16
+ self.assertEquals(self.ad_query.get_single_result,{})
17
+
18
+ def test_dne_all_results(self):
19
+ query = "cn = '%s'" % self.KNOWN_DNE_OBJECT
20
+ self.ad_query.execute_query(where_clause = query)
21
+ self.assertEqual(self.ad_query.get_all_results(), [])
22
+
23
+ def test_single_rowcount(self):
24
+ query = "cn = '%s'" % self.KNOWN_EXISTS_USER
25
+ self.ad_query.execute_query(where_clause = query)
26
+ self.assertEqual(self.ad_query.get_row_count(), 1)
27
+
28
+ def test_single_single_result(self):
29
+ query = "cn = '%s'" % self.KNOWN_EXISTS_USER
30
+ self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query)
31
+ self.assertEqual(self.ad_query.get_single_result()['cn'],self.KNOWN_EXISTS_USER)
32
+
33
+ def test_single_all_results(self):
34
+ query = "cn = '%s'" % self.KNOWN_EXISTS_USER
35
+ self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query)
36
+ self.assertEqual(self.ad_query.get_all_results()[0]['cn'],self.KNOWN_EXISTS_USER)
37
+
38
+ def test_multiple_rowcount(self):
39
+ query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER)
40
+ self.ad_query.execute_query(where_clause = query)
41
+ self.assertEqual(self.ad_query.row_count, 2)
42
+
43
+ def test_multiple_single_result(self):
44
+ query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER)
45
+ self.ad_query.execute_query(where_clause = query)
46
+ self.assertIsInstance(self.ad_query.get_single_result,dict)
47
+
48
+ def test_multiple_all_results(self):
49
+ query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER)
50
+ self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query)
51
+ r = map(lambda x: x['cn'], self.ad_query.get_all_results())
52
+ k = [self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER]
53
+ self.assertEqual(r.sort(),k.sort())
@@ -1,123 +0,0 @@
1
- import pywintypes
2
-
3
- from .adobject import ADObject
4
- from .aduser import ADUser
5
- from .adcomputer import ADComputer
6
- from .adgroup import ADGroup
7
- from . import pyadconstants
8
- from . import pyadutils
9
-
10
-
11
- class ADContainer(ADObject):
12
- def get_children_iter(self, recursive=False, filter=None):
13
- for com_object in self._ldap_adsi_obj:
14
- q = ADObject.from_com_object(com_object)
15
- q.adjust_pyad_type()
16
- if q.type == "organizationalUnit" and recursive:
17
- for c in q.get_children_iter(recursive=recursive):
18
- if not filter or c.__class__ in filter:
19
- yield c
20
- if not filter or q.__class__ in filter:
21
- yield q
22
-
23
- def get_children(self, recursive=False, filter=None) -> list:
24
- """
25
- Iterate over the children objects in the container.
26
-
27
- :param recursive: include children from sub-containers, defaults to False
28
- :type recursive: bool, optional
29
- :param filter: filter to only specific object classes ie ADUser, defaults to None
30
- :type filter: object, optional
31
- :return: a list of all child objects
32
- :rtype: list
33
- """
34
- return list(self.get_children_iter(recursive=recursive, filter=filter))
35
-
36
- def __create_object(self, type_, name):
37
- prefix = "ou" if type_ == "organizationalUnit" else "cn"
38
- prefixed_name = "=".join((prefix, name))
39
- return self._ldap_adsi_obj.Create(type_, prefixed_name)
40
-
41
- def create_user(
42
- self, name, password=None, upn_suffix=None, enable=True, optional_attributes={}
43
- ):
44
- """Create a new user object in the container"""
45
-
46
- try:
47
- if not upn_suffix:
48
- upn_suffix = self.get_domain().get_default_upn()
49
- upn = "@".join((name, upn_suffix))
50
- obj = self.__create_object("user", name)
51
- obj.Put("sAMAccountName", optional_attributes.get("sAMAccountName", name))
52
- obj.Put("userPrincipalName", upn)
53
- obj.SetInfo()
54
- pyadobj = ADUser.from_com_object(obj)
55
- if enable:
56
- pyadobj.enable()
57
- if password:
58
- pyadobj.set_password(password)
59
- pyadobj.set_user_account_control_setting("NORMAL_ACCOUNT", True)
60
- pyadobj.set_user_account_control_setting("PASSWD_NOTREQD", False)
61
- pyadobj.update_attributes(optional_attributes)
62
- return pyadobj
63
- except pywintypes.com_error as e:
64
- pyadutils.pass_up_com_exception(e)
65
-
66
- def create_group(
67
- self, name, security_enabled=True, scope="GLOBAL", optional_attributes={}
68
- ):
69
- """Create a new group object in the container"""
70
-
71
- try:
72
- obj = self.__create_object("group", name)
73
- obj.Put("sAMAccountName", name)
74
- val = pyadconstants.ADS_GROUP_TYPE[scope]
75
- if security_enabled:
76
- val = val | pyadconstants.ADS_GROUP_TYPE["SECURITY_ENABLED"]
77
- obj.Put("groupType", val)
78
- obj.SetInfo()
79
- pyadobj = ADGroup.from_com_object(obj)
80
- pyadobj.update_attributes(optional_attributes)
81
- return pyadobj
82
- except pywintypes.com_error as e:
83
- pyadutils.pass_up_com_exception(e)
84
-
85
- def create_container(self, name, optional_attributes={}):
86
- """Create a new organizational unit in the container"""
87
-
88
- try:
89
- obj = self.__create_object("organizationalUnit", name)
90
- obj.SetInfo()
91
- pyadobj = ADContainer.from_com_object(obj)
92
- pyadobj.update_attributes(optional_attributes)
93
- return pyadobj
94
- except pywintypes.com_error as e:
95
- pyadutils.pass_up_com_exception(e)
96
-
97
- def create_computer(self, name, enable=True, optional_attributes={}):
98
- """Create a new computer object in the container"""
99
-
100
- try:
101
- obj = self.__create_object("computer", name)
102
- obj.Put("sAMAccountName", name + "$")
103
- if enable:
104
- obj.Put("userAccountControl", 4128)
105
- else:
106
- obj.Put("userAccountControl", 4130)
107
- obj.SetInfo()
108
- pyadobj = ADComputer.from_com_object(obj)
109
- if enable:
110
- pyadobj.enable()
111
- pyadobj.update_attributes(optional_attributes)
112
- return pyadobj
113
- except pywintypes.com_error as e:
114
- pyadutils.pass_up_com_exception(e)
115
-
116
- def remove_child(self, child) -> None:
117
- """Removes the child object from the domain"""
118
-
119
- self._ldap_adsi_obj.Delete(child.type, child.prefixed_cn)
120
-
121
-
122
- ADObject._py_ad_object_mappings["organizationalUnit"] = ADContainer
123
- ADObject._py_ad_object_mappings["container"] = ADContainer
File without changes
File without changes
File without changes