├── .gitignore ├── .nojekyll ├── CHANGELOG ├── README.rst ├── doc ├── Makefile ├── _build │ ├── doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ ├── objects.doctree │ │ ├── pyad.doctree │ │ └── test.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ ├── index.html │ │ └── pyad │ │ │ ├── adbase.html │ │ │ ├── adcomputer.html │ │ │ ├── adcontainer.html │ │ │ ├── addomain.html │ │ │ ├── adgroup.html │ │ │ ├── adobject.html │ │ │ └── aduser.html │ │ ├── _sources │ │ ├── index.txt │ │ ├── objects.txt │ │ ├── pyad.txt │ │ └── test.txt │ │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── default.css │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── pyad.html │ │ ├── search.html │ │ ├── searchindex.js │ │ └── test.html ├── _templates │ └── layout.html ├── conf.py ├── index.rst ├── make.bat ├── objects.rst ├── pyad.rst └── test.rst ├── docs ├── .buildinfo ├── .nojekyll ├── _modules │ ├── index.html │ └── pyad │ │ ├── adbase.html │ │ ├── adcomputer.html │ │ ├── adcontainer.html │ │ ├── addomain.html │ │ ├── adgroup.html │ │ ├── adobject.html │ │ └── aduser.html ├── _sources │ ├── index.txt │ ├── objects.txt │ ├── pyad.txt │ └── test.txt ├── _static │ ├── ajax-loader.gif │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── default.css │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── jquery.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── sidebar.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── genindex.html ├── index.html ├── objects.html ├── objects.inv ├── py-modindex.html ├── pyad.html ├── search.html ├── searchindex.js └── test.html ├── pyad ├── __init__.py ├── adbase.py ├── adcomputer.py ├── adcontainer.py ├── addomain.py ├── adgroup.py ├── adobject.py ├── adquery.py ├── adsearch.py ├── aduser.py ├── pyad.py ├── pyadconstants.py ├── pyadexceptions.py ├── pyadutils.py └── tests │ ├── __init__.py │ ├── pyadunittest.py │ ├── tests_adbase.py │ └── tests_adquery.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/.nojekyll -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.5.03 / 2013-7-7 / Zakir Durumeric, Alex Kavanaugh, nifhell 2 | 3 | feature request: connections to Active Directory now be made with alternate credentials 4 | feature request: ADQuery now allows page sizes greater than 1000 records 5 | 6 | 0.5.04 / 2013-7-7 / Zakir Durumeric 7 | 8 | bug report: now possible to connect to objects with commas in distinguishedName by using \, instead of ,. 9 | 10 | 11 | 0.5.05 / 2013-7-18 / Zakir Durumeric 12 | 13 | bug report: the escaped DNs produced by ADQuery are not compatible with the escapes expected by ADObject.from_dn(). ADObject is fixed to allow both types of escaping. 14 | reported by: Jorrit Jorritsma 15 | 16 | 0.5.06 / 2013-7-28 / Zakir Durumeric 17 | 18 | adding get_uSNChanged() helper to ADObject in order to encapsulate Microsoft's bizarre Integer8 format. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | pyad is a Python library designed to provide a simple, Pythonic interface to Active Directory through ADSI on the Windows platform. Complete documentation can be found at http://zakird.github.io/pyad/. Code is maintained at https://github.com/zakird/pyad. The library can be downloaded from PyPI at https://pypi.python.org/pypi/pyad. 5 | 6 | 7 | Requirements 8 | ============ 9 | 10 | pyad requires pywin32, available at https://github.com/mhammond/pywin32. 11 | 12 | Alternatively, 13 | :: 14 | pip install pywin32 15 | 16 | works as well. 17 | 18 | 19 | Connecting to Active Directory 20 | ============================== 21 | 22 | By default, pyad will connect to the Active Directory domain to which the machine is joined (rootDSE):: 23 | 24 | from pyad import aduser 25 | user = aduser.ADUser.from_cn("myuser") 26 | 27 | 28 | However, it is possible to connect to a specific domain controller or to use alternate credentials, by calling pyad.set_defaults() or by passing in connection information in the options dictionary for each object you connect to. Authentication is performed over a secured connection, pyad will not pass credentials over clear text. The following options can be set in the `set_defaults` call: `ldap_server`, `gc_server`, `ldap_port`, `gc_port`, `username`, `password`, and `ssl` (True/False). For example, the following code will set the default connection parameters for all objects accessed through pyad:: 29 | 30 | from pyad import * 31 | pyad.set_defaults(ldap_server="dc1.domain.com", username="service_account", password="mypassword") 32 | user = pyad.aduser.ADUser.from_cn("myuser") 33 | 34 | 35 | It is also possible to pass in options when connecting to a specific object. This will not set the library defaults, but these settings will be used from any objects you derive from it (e.g. if you request group membership of a user) Example:: 36 | 37 | from pyad import aduser 38 | user = aduser.ADUser.from_cn("myuser", options=dict(ldap_server="dc1.domain.com")) 39 | 40 | 41 | Basic Object Manipulation 42 | ========================= 43 | 44 | There are first order Python classes for different types of objects in Active Directory. For example, ADUser represents user objects and ADGroup represents groups. All objects subclass ADObject. Most methods are defined in ADObject, but subclasses generally provide additional helper methods (e.g. ADUser has `set_password` and ADGroup has `add_member`). 45 | 46 | It is possible to connect to an object by distinguished name, CN, UPN, and GUID if you already know the type of object. Examples:: 47 | 48 | from pyad import aduser 49 | user1 = aduser.ADUser.from_dn("cn=myuser, ou=staff, dc=domain, dc=com") 50 | user2 = aduser.ADUser.from_cn("myuser") 51 | user3 = aduser.ADUser.from_guid("XXX-XXX-XXX") 52 | 53 | 54 | It is also possible to use the pyad factory with an arbitrary Active Directory object and to receive an appropriately classed Python object:: 55 | 56 | from pyad import pyad 57 | user = pyad.from_cn("user1") 58 | computer = pyad.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") 59 | group = pyad.from_guid("XXX-XXX-XXX") 60 | 61 | 62 | Unlike the ADSI interface, pyad objects are intended to interact with one another. Instead of adding the DN of a user to the members attribute of a group to add the user, you instead add the user object to the group. For instance:: 63 | 64 | user1 = ADUser.from_cn("myuser1") 65 | user2 = ADUser.from_cn("myuser2") 66 | group = ADGroup.from_dn("staff") 67 | 68 | group.add_members([user1, user2]) 69 | 70 | for user in group.get_members(): 71 | print user1.description 72 | 73 | 74 | However, it is still possible to directly manipulate any attribute outside of the helper methods that pyad provides:: 75 | 76 | user1 = ADUser.from_cn("myuser1") 77 | user.set_attribute("description", "new description") 78 | user.append_to_attribute("member", "cn=myuser1, ou=staff, dc=domain, dc=com") 79 | 80 | 81 | More details on how to manipulate the objects you find to is found in the next section. 82 | 83 | 84 | Creating, Moving, and Deleting Objects 85 | ====================================== 86 | 87 | There are two methodologies for creating and deleting objects. In both cases, you must first bind to the parent container. When creating a new object, several attributes are required, but other additional attributes can be specified with the `optional_attributes` parameter. Example 1:: 88 | 89 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 90 | 91 | # create a new group without any optional attributes 92 | new_computer = ADComputer.create("WS-489", ou) 93 | 94 | # create a new group with additional attributes 95 | new_group = ADGroup.create("IT-STAFF", security_enabled=True, scope='UNIVERSAL', 96 | optional_attributes = {"description":"all IT staff in our company"}) 97 | 98 | It is also possible to create new objects from the parent container:: 99 | 100 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 101 | computer = ou.create_computer("WS-490") 102 | 103 | Once objects are created, they can be moved:: 104 | 105 | computer = ADComputer.from_cn("WS-500") 106 | computer.move(ADContainer.from_dn("ou=workstations, ou=HR, dc=company, dc=com")) 107 | 108 | or renamed:: 109 | 110 | computer = ADComputer.from_cn("WS-500") 111 | computer.rename("WS-501") 112 | 113 | Objects can be removed by calling delete():: 114 | 115 | ADComputer.from_cn("WS-500").delete() 116 | 117 | 118 | Searching Active Directory 119 | ========================== 120 | 121 | As shown above, objects can be directly connected to via CN, DN, GUID, or UPN. However, objects can also be searched for through the ADQuery interface (and in the background, this is how objects are actually found when you connect by CN). It is important to note that the ADQuery interface will not provide you with pyad objects, but instead with only the attributes for which you queried, for performance reasons. Example:: 122 | 123 | import pyad.adquery 124 | q = pyad.adquery.ADQuery() 125 | 126 | q.execute_query( 127 | attributes = ["distinguishedName", "description"], 128 | where_clause = "objectClass = '*'", 129 | base_dn = "OU=users, DC=domain, DC=com" 130 | ) 131 | 132 | for row in q.get_results(): 133 | print row["distinguishedName"] 134 | 135 | License 136 | ======= 137 | 138 | pyad is licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 139 | 140 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 141 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonActiveDirectoryToolspyad.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonActiveDirectoryToolspyad.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonActiveDirectoryToolspyad" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonActiveDirectoryToolspyad" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /doc/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /doc/_build/doctrees/objects.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/doctrees/objects.doctree -------------------------------------------------------------------------------- /doc/_build/doctrees/pyad.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/doctrees/pyad.doctree -------------------------------------------------------------------------------- /doc/_build/doctrees/test.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/doctrees/test.doctree -------------------------------------------------------------------------------- /doc/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 2953a53581c40a741a5dca9afb6c7053 4 | tags: a205e9ed8462ae86fdd2f73488852ba9 5 | -------------------------------------------------------------------------------- /doc/_build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Overview: module code — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |

All modules for which code is available

48 | 56 | 57 |
58 |
59 |
60 |
61 |
62 | 74 | 75 |
76 |
77 |
78 |
79 | 91 | 92 | 96 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /doc/_build/html/_modules/pyad/addomain.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pyad.addomain — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 43 | 44 |
45 |
46 |
47 |
48 | 49 |

Source code for pyad.addomain

 50 | from adcontainer import *
 51 | 
 52 | 
[docs]class ADDomain(ADContainer): 53 | 54 |
[docs] def get_default_upn(self): 55 | """Returns the default userPrincipalName for the domain.""" 56 | self._ldap_adsi_obj.GetInfoEx(["canonicalName",],0) 57 | return self._ldap_adsi_obj.get("canonicalName").rstrip('/') 58 |
59 | def __get_domain(self): 60 | if self._domain_pyad_obj is None: 61 | domain_path = 'dc=' + self.dn.lower().split("dc=",1)[1] 62 | self._domain_pyad_obj = ADDomain.from_dn(domain_path, 63 | options={'server':self.default_ldap_server,'port':self.default_ldap_port}) 64 | return self._domain_pyad_obj 65 | ADObject.get_domain = __get_domain 66 | 67 | ADObject._py_ad_object_mappings['domain'] = ADDomain 68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 87 | 88 |
89 |
90 |
91 |
92 | 105 | 106 | 110 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /doc/_build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. Python Active Directory Tools (pyad) documentation master file, created by 2 | sphinx-quickstart on Sat Jul 6 13:48:15 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Python Active Directory Tools's Documentation 7 | ==================================================== 8 | 9 | pyad is a python library designed to provide a simple, object oriented interface to Active Directory through ADSI on the Windows platform. The code for this project is maintained at https://github.com/zakird/pyad and can be downloaded at https://pypi.python.org/pypi/pyad. 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | 15 | pyad 16 | 17 | objects 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | 27 | Requirements 28 | ============ 29 | 30 | pyad requires pywin32, which is available at http://sourceforge.net/projects/pywin32. 31 | 32 | License 33 | ======= 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. -------------------------------------------------------------------------------- /doc/_build/html/_sources/objects.txt: -------------------------------------------------------------------------------- 1 | Manipulating Active Directory Objects 2 | ===================================== 3 | 4 | This page contains detailed information about how to manipulate various types of Active Directory objects. Keep in mind that all objects subclass ADObject. 5 | 6 | ADObject 7 | --------- 8 | 9 | .. autoclass:: pyad.adobject.ADObject 10 | :members: 11 | 12 | 13 | ADUser 14 | ---------------- 15 | 16 | .. autoclass:: pyad.aduser.ADUser 17 | :members: 18 | 19 | ADComputer 20 | ------------- 21 | 22 | .. autoclass:: pyad.adcomputer.ADComputer 23 | :members: 24 | 25 | ADGroup 26 | ------- 27 | 28 | .. autoclass:: pyad.adgroup.ADGroup 29 | :members: 30 | 31 | ADContainer 32 | ------------------ 33 | 34 | .. autoclass:: pyad.adcontainer.ADContainer 35 | :members: 36 | 37 | ADDomain 38 | -------- 39 | 40 | NOTE: ADDomain subclasses ADContainer. 41 | 42 | .. autoclass:: pyad.addomain.ADDomain 43 | :members: -------------------------------------------------------------------------------- /doc/_build/html/_sources/pyad.txt: -------------------------------------------------------------------------------- 1 | Active Directory Basics 2 | ======================= 3 | 4 | Pyad is designed to expose the ADSI interface to Microsoft Active Directory in a straight-forward Pythonic manner. The library is designed to run on Windows. This page describes the basics of how to use the library. It does not, however, comprehensively describe the functionality of the library, which is more aptly documented in the next section. 5 | 6 | 7 | Connecting to Active Directory 8 | ------------------------------ 9 | 10 | By default, pyad will connect to the Active Directory domain to which the machine is joined (rootDSE):: 11 | 12 | from pyad import aduser 13 | user = aduser.ADUser.from_cn("myuser") 14 | 15 | However, it is possible to connect to a specific domain controller or to use alternate credentials, by calling pyad.set_defaults() or by passing in connection information in the options dictionary for each object you connect to. Authentication is performed over a secured connection, pyad will not pass credentials over clear text. The following options can be set in the `set_defaults` call: `ldap_server`, `gc_server`, `ldap_port`, `gc_port`, `username`, `password`, and `ssl` (True/False). For example, the following code will set the default connection parameters for all objects accessed through pyad:: 16 | 17 | from pyad import * 18 | pyad.set_defaults(ldap_server="dc1.domain.com", username="service_account", password="mypassword") 19 | user = pyad.aduser.ADUser.from_cn("myuser") 20 | 21 | It is also possible to pass in options when connecting to a specific object. This will not set the library defaults, but these settings will be used from any objects you derive from it (e.g. if you request group membership of a user) Example:: 22 | 23 | from pyad import aduser 24 | user = aduser.ADUser.from_cn("myuser", options=dict(ldap_server="dc1.domain.com")) 25 | 26 | Basic Object Manipulation 27 | ------------------------- 28 | 29 | There are first order Python classes for different types of objects in Active Directory. For example, ADUser represents user objects and ADGroup represents groups. All objects subclass ADObject. Most methods are defined in ADObject, but subclasses generally provide additional helper methods (e.g. ADUser has `set_password` and ADGroup has `add_member`). 30 | 31 | It is possible to connect to an object by distinguished name, CN, UPN, and GUID if you already know the type of object. Examples:: 32 | 33 | from pyad import aduser 34 | user1 = aduser.ADUser.from_dn("cn=myuser, ou=staff, dc=domain, dc=com") 35 | user2 = aduser.ADUser.from_cn("myuser") 36 | user3 = aduser.ADUser.from_guid("XXX-XXX-XXX") 37 | 38 | It is also possible to use the pyad factory with an arbitrary Active Directory object and to receive an appropriately classed Python object:: 39 | 40 | from pyad import pyad 41 | user = pyad.from_cn("user1") 42 | computer = pyad.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") 43 | group = pyad.from_guid("XXX-XXX-XXX") 44 | 45 | Unlike the ADSI interface, pyad objects are intended to interact with one another. Instead of adding the DN of a user to the members attribute of a group to add the user, you instead add the user object to the group. For instance:: 46 | 47 | user1 = ADUser.from_cn("myuser1") 48 | user2 = ADUser.from_cn("myuser2") 49 | group = ADGroup.from_dn("staff") 50 | 51 | group.add_members([user1, user2]) 52 | 53 | for user in group.get_members(): 54 | print user1.description 55 | 56 | However, it is still possible to directly manipulate any attribute outside of the helper methods that pyad provides:: 57 | 58 | user1 = ADUser.from_cn("myuser1") 59 | user.set_attribute("description", "new description") 60 | user.append_to_attribute("member", "cn=myuser1, ou=staff, dc=domain, dc=com") 61 | 62 | 63 | More details on how to manipulate the objects you find to is found in the next section. 64 | 65 | 66 | 67 | Creating, Moving, and Deleting Objects 68 | ---------------------------------- 69 | 70 | There are two methodologies for creating and deleting objects. In both cases, you must first bind to the parent container. When creating a new object, several attributes are required, but other additional attributes can be specified with the `optional_attributes` parameter. Example 1:: 71 | 72 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 73 | 74 | # create a new group without any optional attributes 75 | new_computer = ADComputer.create("WS-489", ou) 76 | 77 | # create a new group with additional attributes 78 | new_group = ADGroup.create("IT-STAFF", security_enabled=True, scope='UNIVERSAL', 79 | optional_attributes = {"description":"all IT staff in our company"}) 80 | 81 | It is also possible to create new objects from the parent container:: 82 | 83 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 84 | computer = ou.create_computer("WS-490") 85 | 86 | Once objects are created, they can be moved:: 87 | 88 | computer = ADComputer.from_cn("WS-500") 89 | computer.move(ADContainer.from_dn("ou=workstations, ou=HR, dc=company, dc=com")) 90 | 91 | or renamed:: 92 | 93 | computer = ADComputer.from_cn("WS-500") 94 | computer.rename("WS-501") 95 | 96 | Objects can be removed by calling delete():: 97 | 98 | ADComputer.from_cn("WS-500").delete() 99 | 100 | 101 | Searching Active Directory 102 | -------------------------- 103 | 104 | As shown above, objects can be directly connected to via CN, DN, GUID, or UPN. However, objects can also be searched for through the ADQuery interface (and in the background, this is how objects are actually found when you connect by CN). It is important to note that the ADQuery interface will not provide you with pyad objects, but instead with only the attributes for which you queried, for performance reasons. Example:: 105 | 106 | import pyad.adquery 107 | q = pyad.adquery.ADQuery() 108 | 109 | q.execute_query( 110 | attributes = ["distinguishedName", "description"], 111 | where_clause = "objectClass = '*'", 112 | base_dn = "OU=users, DC=domain, DC=com" 113 | ) 114 | 115 | for row in q.get_results(): 116 | print row["distinguishedName"] 117 | -------------------------------------------------------------------------------- /doc/_build/html/_sources/test.txt: -------------------------------------------------------------------------------- 1 | .. automodule:: pyad.adbase 2 | :members: 3 | 4 | .. automodule:: pyad.aduser 5 | :members: -------------------------------------------------------------------------------- /doc/_build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /doc/_build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /doc/_build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/comment-close.png -------------------------------------------------------------------------------- /doc/_build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/comment.png -------------------------------------------------------------------------------- /doc/_build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /doc/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | window.setTimeout(function() { 172 | $.each(terms, function() { 173 | body.highlightText(this.toLowerCase(), 'highlighted'); 174 | }); 175 | }, 10); 176 | $('') 178 | .appendTo($('#searchbox')); 179 | } 180 | }, 181 | 182 | /** 183 | * init the domain index toggle buttons 184 | */ 185 | initIndexTable : function() { 186 | var togglers = $('img.toggler').click(function() { 187 | var src = $(this).attr('src'); 188 | var idnum = $(this).attr('id').substr(7); 189 | $('tr.cg-' + idnum).toggle(); 190 | if (src.substr(-9) == 'minus.png') 191 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 192 | else 193 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 194 | }).css('display', ''); 195 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 196 | togglers.click(); 197 | } 198 | }, 199 | 200 | /** 201 | * helper function to hide the search marks again 202 | */ 203 | hideSearchWords : function() { 204 | $('#searchbox .highlight-link').fadeOut(300); 205 | $('span.highlighted').removeClass('highlighted'); 206 | }, 207 | 208 | /** 209 | * make the url absolute 210 | */ 211 | makeURL : function(relativeURL) { 212 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 213 | }, 214 | 215 | /** 216 | * get the current relative url 217 | */ 218 | getCurrentURL : function() { 219 | var path = document.location.pathname; 220 | var parts = path.split(/\//); 221 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 222 | if (this == '..') 223 | parts.pop(); 224 | }); 225 | var url = parts.join('/'); 226 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 227 | } 228 | }; 229 | 230 | // quick alias for translations 231 | _ = Documentation.gettext; 232 | 233 | $(document).ready(function() { 234 | Documentation.init(); 235 | }); 236 | -------------------------------------------------------------------------------- /doc/_build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /doc/_build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/down.png -------------------------------------------------------------------------------- /doc/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/file.png -------------------------------------------------------------------------------- /doc/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/minus.png -------------------------------------------------------------------------------- /doc/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/doc/_build/html/_static/plus.png -------------------------------------------------------------------------------- /doc/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /doc/_build/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k 3 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 |

Python Module Index

52 | 53 |
54 | p 55 |
56 | 57 | 58 | 59 | 61 | 62 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 77 |
 
60 | p
65 | pyad 66 |
    70 | pyad.adbase 71 |
    75 | pyad.aduser 76 |
78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 | 97 | 98 |
99 |
100 |
101 |
102 | 114 | 115 | 119 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /doc/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 101 | 105 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /doc/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({envversion:42,terms:{all:[0,2,3],code:[1,3],queri:[0,2,3],get_creat:2,prefix:2,create_us:2,force_pwd_change_on_login:2,follow:3,children:2,row:3,get_group_scop:2,unnumb:[],remove_all_memb:2,readabl:2,ws1:3,under:1,mycomput:2,aspx:2,sourc:[0,2],string:2,fals:[2,3],set_group_typ:2,util:0,govern:1,join:3,ldap_serv:3,upn:3,list:2,iter:2,add_memb:[2,3],item:[],adjust:[],impli:1,myuser:3,aptli:3,sourceforg:1,design:[1,3],pass:3,download:1,further:2,append:2,index:1,from_guid:[2,3],section:3,current:2,delet:[],version:1,"new":[2,3],net:1,method:3,create_group:2,deriv:3,gener:[2,3],path:[],valu:2,search:[],set_group_scop:2,datetim:2,chang:2,via:3,ads_user_flag:2,ldap_port:3,modul:1,"boolean":2,unit:2,from:[2,3],describ:3,univers:[2,3],two:3,next:[2,3],live:2,call:3,scope:[2,3],type:[2,3],more:[2,3],ital:[],rootds:3,particular:2,known:2,compani:3,must:[2,3],none:2,word:[],paragraph:[],can:[1,2,3],control:[2,3],mycontain:2,give:[],sudo:[],accept:2,remove_child:2,remove_memb:2,parent_container_path:2,multipl:2,secur:[2,3],anoth:3,from_com_object:2,write:1,how:[2,3],sid:2,instead:[2,3],simpl:1,updat:2,adcontain:[],msdn:2,set_samaccountnam:2,membership:[2,3],new_typ:2,mai:[1,2],law:1,github:1,classmethod:2,bind:3,create_contain:2,credenti:3,element:2,execute_queri:3,maintain:1,allow:2,order:3,talk:[],help:0,get_mandatory_attribut:2,dump_to_xml:2,get_domain:2,through:[1,2,3],adus:[],still:3,paramet:3,getattribut:2,group:[2,3],valuestoremov:2,platform:1,window:[1,3],comprehens:3,filter_:2,main:[],easier:2,set_managedbi:2,them:2,"return":2,thei:[2,3],name:[2,3],authent:3,service_account:3,each:3,found:[2,3],domain:[2,3],zakird:1,connect:[],our:3,orient:1,shown:3,agre:1,content:1,print:3,factori:3,manipul:[],differ:3,reason:3,base:[0,2],dictionari:[2,3],org:1,workstat:3,basi:1,synchron:2,keep:2,length:[],outsid:3,organiz:2,first:3,softwar:1,directli:3,onc:3,number:[],date:2,unlik:3,alreadi:3,get_group_typ:2,new_comput:3,given:2,interact:3,least:[],similarli:2,store:[0,2],schema:2,option:[2,3],methodolog:3,adjust_pyad_typ:[],from_dn:[2,3],specifi:[2,3],myuser2:3,myuser1:3,kind:1,sync_membership:2,keyword:2,provid:[1,3],remov:[2,3],project:1,adbas:0,parent_contain:2,userprincipalnam:2,comput:[2,3],behavior:2,mind:2,argument:2,packag:0,blacklist_attribut:2,expir:2,have:[],need:2,is_member_of:2,inform:[2,3],no_flush:2,note:[2,3],also:3,without:[1,3],which:[1,2,3],guid_str:2,singl:2,copi:1,unless:1,distribut:[1,2],usernam:3,who:2,most:[2,3],automodul:[],prefixed_cn:2,"class":[0,2,3],addomain:[],renam:[2,3],set_user_account_control_set:2,objectclass:3,clear:[2,3],request:3,doe:[2,3],determin:2,get_password_last_set:2,microsoft:[2,3],set_expir:2,text:3,permiss:1,new_ou_object:2,find:3,staff:3,xml:2,access:3,onli:[2,3],nobodi:2,whitelist_attribut:2,transact:2,apach:1,should:2,from_cn:3,dict:3,ldap:2,local:2,over:[2,3],adcomput:[],move:[],variou:2,get:2,express:1,pypi:1,com_object:2,obtain:1,ssl:3,requir:[],bar:[],enabl:2,userflag:2,useraccountcontrol:2,whether:2,contain:[2,3],where:2,usnchang:2,set:[2,3],dump:2,base_dn:3,mutabl:2,see:[1,2],mandatori:2,valuestoappend:2,managedbi:2,kei:2,"import":3,attribut:[2,3],altern:3,check_memb:2,distinguish:[2,3],addit:3,both:3,last:2,pyhon:[],howev:3,instanc:3,login:2,com:[1,2,3],distinguished_nam:2,guid:[2,3],empti:2,basic:[],getex:2,new_scop:2,xxx:3,get_result:3,ani:[1,2,3],child:2,new_popul:2,"case":[2,3],multi:2,ident:2,straight:3,remove_from_attribut:2,defin:[2,3],abov:3,manner:3,get_attribut:2,helper:3,itself:[],aa772300:2,sever:3,parent:[2,3],adgroup:[],perform:3,remove_from_group:2,belong:2,same:2,member:[2,3],new_group:3,http:[1,2],adqueri:3,gc_port:3,nest:2,wherea:2,get_user_account_control_set:2,user:[2,3],rremov:2,travers:2,appropri:3,entri:2,get_allowed_attribut:2,always_return_list:2,exampl:[],command:[],container_object:2,undefin:2,set_attribut:3,just:2,mypassword:3,distinguishednam:[2,3],pywin32:1,optional_attribut:[2,3],human:2,languag:1,expos:3,upn_suffix:2,set_password:[2,3],get_children:2,add:[2,3],match:2,applic:1,update_attribut:2,know:3,background:3,password:[2,3],recurs:2,clear_attribut:2,like:[],specif:[1,3],arbitrari:3,safest:2,pyad:[],either:[1,2],page:[1,2,3],create_comput:[2,3],www:1,ignoregroup:2,back:[],global:2,sampl:[],integ:2,adobject:[],get_memb:[2,3],guarante:2,librari:[1,2,3],add_to_group:2,insensit:2,get_default_upn:2,adspath:2,subclass:[2,3],adsi_ldap_com_object:2,leav:2,condit:1,foo:[],machin:3,object:[],run:3,bold:[],get_usnchang:2,attribute_value_dict:2,"super":[],about:2,actual:3,append_to_attribut:[2,3],choke:2,disabl:2,emphasi:[],within:[0,2],automat:[],warranti:1,check_contains_memb:2,avail:1,get_memberof:2,interfac:[1,3],forward:3,"function":3,forc:2,regard:2,get_optional_attribut:2,"true":[2,3],reset:2,possibl:3,new_nam:2,limit:1,otherwis:2,similar:2,expect:2,user2:3,user3:3,creat:[],user1:3,repres:[2,3],exist:2,check:2,adsi:[1,2,3],quot:[],when:[2,3],detail:[2,3],power:[],"default":[0,2,3],other:3,set_default:3,you:[1,2,3],dc1:3,intend:3,newvalu:2,security_en:[2,3],experienc:2,receiv:3,longer:2,descript:3,gc_server:3,where_claus:3,ignor:2,clear_managedbi:2,potenti:2,time:2,organizationalunit:2},objtypes:{"0":"py:module","1":"py:method","2":"py:class","3":"py:classmethod","4":"py:attribute"},objnames:{"0":["py","module","Python module"],"1":["py","method","Python method"],"2":["py","class","Python class"],"3":["py","classmethod","Python class method"],"4":["py","attribute","Python attribute"]},filenames:["test","index","objects","pyad"],titles:["<no title>","Python Active Directory Tools’s Documentation","Manipulating Active Directory Objects","Active Directory Basics"],objects:{"pyad.addomain.ADDomain":{get_default_upn:[2,1,1,""]},"pyad.adobject":{ADObject:[2,2,1,""]},"pyad.adobject.ADObject":{parent_container:[2,4,1,""],rename:[2,1,1,""],dump_to_xml:[2,1,1,""],get_domain:[2,1,1,""],set_user_account_control_setting:[2,1,1,""],is_member_of:[2,1,1,""],parent_container_path:[2,4,1,""],move:[2,1,1,""],guid:[2,4,1,""],update_attributes:[2,1,1,""],get_user_account_control_settings:[2,1,1,""],remove_from_group:[2,1,1,""],from_com_object:[2,3,1,""],sid:[2,4,1,""],get_mandatory_attributes:[2,1,1,""],add_to_group:[2,1,1,""],type:[2,4,1,""],dn:[2,4,1,""],enable:[2,1,1,""],get_attribute:[2,1,1,""],adsPath:[2,4,1,""],guid_str:[2,4,1,""],set_managedby:[2,1,1,""],update_attribute:[2,1,1,""],disable:[2,1,1,""],get_allowed_attributes:[2,1,1,""],clear_attribute:[2,1,1,""],prefixed_cn:[2,4,1,""],remove_from_attribute:[2,1,1,""],from_guid:[2,3,1,""],from_dn:[2,3,1,""],get_uSNChanged:[2,1,1,""],get_memberOfs:[2,1,1,""],clear_managedby:[2,1,1,""],get_optional_attributes:[2,1,1,""],append_to_attribute:[2,1,1,""],"delete":[2,1,1,""]},"pyad.adcontainer":{ADContainer:[2,2,1,""]},"pyad.adgroup":{ADGroup:[2,2,1,""]},pyad:{aduser:[0,0,1,""],adbase:[0,0,1,""]},"pyad.aduser":{ADUser:[2,2,1,""]},"pyad.adcomputer.ADComputer":{create:[2,3,1,""],get_creator:[2,1,1,""]},"pyad.adbase":{ADBase:[0,2,1,""]},"pyad.addomain":{ADDomain:[2,2,1,""]},"pyad.adcomputer":{ADComputer:[2,2,1,""]},"pyad.aduser.ADUser":{create:[2,3,1,""],get_password_last_set:[2,1,1,""],set_password:[2,1,1,""],set_expiration:[2,1,1,""],force_pwd_change_on_login:[2,1,1,""]},"pyad.adcontainer.ADContainer":{create_group:[2,1,1,""],get_children:[2,1,1,""],create_user:[2,1,1,""],remove_child:[2,1,1,""],create_computer:[2,1,1,""],create_container:[2,1,1,""]},"pyad.adgroup.ADGroup":{sync_membership:[2,1,1,""],check_contains_member:[2,1,1,""],get_group_type:[2,1,1,""],create:[2,3,1,""],get_group_scope:[2,1,1,""],set_group_type:[2,1,1,""],remove_all_members:[2,1,1,""],add_members:[2,1,1,""],set_group_scope:[2,1,1,""],remove_members:[2,1,1,""],get_members:[2,1,1,""]}},titleterms:{adgroup:2,creat:3,tabl:1,indic:1,connect:3,adobject:2,adus:2,subject:[],adcontain:2,titl:0,licens:1,activ:[1,2,3],delet:3,basic:3,document:1,adcomput:2,manipul:[2,3],python:1,tool:1,object:[2,3],inlin:[],requir:1,search:3,addomain:2,subtitl:[],move:3,directori:[1,2,3],markup:[],pyad:[],exampl:[],thi:[]}}) -------------------------------------------------------------------------------- /doc/_build/html/test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <no title> — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 | class pyad.adbase.ADBase[source]
50 |

Base class that is utilized by all objects within package to help 51 | store defaults. (search, query, all AD objects)

52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 |
60 |
61 |

This Page

62 | 66 | 78 | 79 |
80 |
81 |
82 |
83 | 95 | 96 | 100 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /doc/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block footer %} 4 | {{ super() }} 5 | 18 | {% endblock %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. Python Active Directory Tools (pyad) documentation master file, created by 2 | sphinx-quickstart on Sat Jul 6 13:48:15 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Python Active Directory Tools's Documentation 7 | ==================================================== 8 | 9 | pyad is a python library designed to provide a simple, object oriented interface to Active Directory through ADSI on the Windows platform. The code for this project is maintained at https://github.com/zakird/pyad and can be downloaded at https://pypi.python.org/pypi/pyad. 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | 15 | pyad 16 | 17 | objects 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | 27 | Requirements 28 | ============ 29 | 30 | pyad requires pywin32, which is available at http://sourceforge.net/projects/pywin32. 31 | 32 | License 33 | ======= 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PythonActiveDirectoryToolspyad.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PythonActiveDirectoryToolspyad.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /doc/objects.rst: -------------------------------------------------------------------------------- 1 | Manipulating Active Directory Objects 2 | ===================================== 3 | 4 | This page contains detailed information about how to manipulate various types of Active Directory objects. Keep in mind that all objects subclass ADObject. 5 | 6 | ADObject 7 | --------- 8 | 9 | .. autoclass:: pyad.adobject.ADObject 10 | :members: 11 | 12 | 13 | ADUser 14 | ---------------- 15 | 16 | .. autoclass:: pyad.aduser.ADUser 17 | :members: 18 | 19 | ADComputer 20 | ------------- 21 | 22 | .. autoclass:: pyad.adcomputer.ADComputer 23 | :members: 24 | 25 | ADGroup 26 | ------- 27 | 28 | .. autoclass:: pyad.adgroup.ADGroup 29 | :members: 30 | 31 | ADContainer 32 | ------------------ 33 | 34 | .. autoclass:: pyad.adcontainer.ADContainer 35 | :members: 36 | 37 | ADDomain 38 | -------- 39 | 40 | NOTE: ADDomain subclasses ADContainer. 41 | 42 | .. autoclass:: pyad.addomain.ADDomain 43 | :members: -------------------------------------------------------------------------------- /doc/pyad.rst: -------------------------------------------------------------------------------- 1 | Active Directory Basics 2 | ======================= 3 | 4 | Pyad is designed to expose the ADSI interface to Microsoft Active Directory in a straight-forward Pythonic manner. The library is designed to run on Windows. This page describes the basics of how to use the library. It does not, however, comprehensively describe the functionality of the library, which is more aptly documented in the next section. 5 | 6 | 7 | Connecting to Active Directory 8 | ------------------------------ 9 | 10 | By default, pyad will connect to the Active Directory domain to which the machine is joined (rootDSE):: 11 | 12 | from pyad import aduser 13 | user = aduser.ADUser.from_cn("myuser") 14 | 15 | However, it is possible to connect to a specific domain controller or to use alternate credentials, by calling pyad.set_defaults() or by passing in connection information in the options dictionary for each object you connect to. Authentication is performed over a secured connection, pyad will not pass credentials over clear text. The following options can be set in the `set_defaults` call: `ldap_server`, `gc_server`, `ldap_port`, `gc_port`, `username`, `password`, and `ssl` (True/False). For example, the following code will set the default connection parameters for all objects accessed through pyad:: 16 | 17 | from pyad import * 18 | pyad.set_defaults(ldap_server="dc1.domain.com", username="service_account", password="mypassword") 19 | user = pyad.aduser.ADUser.from_cn("myuser") 20 | 21 | It is also possible to pass in options when connecting to a specific object. This will not set the library defaults, but these settings will be used from any objects you derive from it (e.g. if you request group membership of a user) Example:: 22 | 23 | from pyad import aduser 24 | user = aduser.ADUser.from_cn("myuser", options=dict(ldap_server="dc1.domain.com")) 25 | 26 | Basic Object Manipulation 27 | ------------------------- 28 | 29 | There are first order Python classes for different types of objects in Active Directory. For example, ADUser represents user objects and ADGroup represents groups. All objects subclass ADObject. Most methods are defined in ADObject, but subclasses generally provide additional helper methods (e.g. ADUser has `set_password` and ADGroup has `add_member`). 30 | 31 | It is possible to connect to an object by distinguished name, CN, UPN, and GUID if you already know the type of object. Examples:: 32 | 33 | from pyad import aduser 34 | user1 = aduser.ADUser.from_dn("cn=myuser, ou=staff, dc=domain, dc=com") 35 | user2 = aduser.ADUser.from_cn("myuser") 36 | user3 = aduser.ADUser.from_guid("XXX-XXX-XXX") 37 | 38 | It is also possible to use the pyad factory with an arbitrary Active Directory object and to receive an appropriately classed Python object:: 39 | 40 | from pyad import pyad 41 | user = pyad.from_cn("user1") 42 | computer = pyad.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") 43 | group = pyad.from_guid("XXX-XXX-XXX") 44 | 45 | Unlike the ADSI interface, pyad objects are intended to interact with one another. Instead of adding the DN of a user to the members attribute of a group to add the user, you instead add the user object to the group. For instance:: 46 | 47 | user1 = ADUser.from_cn("myuser1") 48 | user2 = ADUser.from_cn("myuser2") 49 | group = ADGroup.from_dn("staff") 50 | 51 | group.add_members([user1, user2]) 52 | 53 | for user in group.get_members(): 54 | print user1.description 55 | 56 | However, it is still possible to directly manipulate any attribute outside of the helper methods that pyad provides:: 57 | 58 | user1 = ADUser.from_cn("myuser1") 59 | user.set_attribute("description", "new description") 60 | user.append_to_attribute("member", "cn=myuser1, ou=staff, dc=domain, dc=com") 61 | 62 | 63 | More details on how to manipulate the objects you find to is found in the next section. 64 | 65 | 66 | 67 | Creating, Moving, and Deleting Objects 68 | ---------------------------------- 69 | 70 | There are two methodologies for creating and deleting objects. In both cases, you must first bind to the parent container. When creating a new object, several attributes are required, but other additional attributes can be specified with the `optional_attributes` parameter. Example 1:: 71 | 72 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 73 | 74 | # create a new group without any optional attributes 75 | new_computer = ADComputer.create("WS-489", ou) 76 | 77 | # create a new group with additional attributes 78 | new_group = ADGroup.create("IT-STAFF", security_enabled=True, scope='UNIVERSAL', 79 | optional_attributes = {"description":"all IT staff in our company"}) 80 | 81 | It is also possible to create new objects from the parent container:: 82 | 83 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 84 | computer = ou.create_computer("WS-490") 85 | 86 | Once objects are created, they can be moved:: 87 | 88 | computer = ADComputer.from_cn("WS-500") 89 | computer.move(ADContainer.from_dn("ou=workstations, ou=HR, dc=company, dc=com")) 90 | 91 | or renamed:: 92 | 93 | computer = ADComputer.from_cn("WS-500") 94 | computer.rename("WS-501") 95 | 96 | Objects can be removed by calling delete():: 97 | 98 | ADComputer.from_cn("WS-500").delete() 99 | 100 | 101 | Searching Active Directory 102 | -------------------------- 103 | 104 | As shown above, objects can be directly connected to via CN, DN, GUID, or UPN. However, objects can also be searched for through the ADQuery interface (and in the background, this is how objects are actually found when you connect by CN). It is important to note that the ADQuery interface will not provide you with pyad objects, but instead with only the attributes for which you queried, for performance reasons. Example:: 105 | 106 | import pyad.adquery 107 | q = pyad.adquery.ADQuery() 108 | 109 | q.execute_query( 110 | attributes = ["distinguishedName", "description"], 111 | where_clause = "objectClass = '*'", 112 | base_dn = "OU=users, DC=domain, DC=com" 113 | ) 114 | 115 | for row in q.get_results(): 116 | print row["distinguishedName"] 117 | 118 | When you search an AD Forest for users you will likely want to search on "CN", you can simulate a SQL 'LIKE' where clause with the AD wildcard character '*'. E.g. cn='\*john\*'. :: 119 | 120 | import pyad.adquery as adquery 121 | import pprint 122 | 123 | q = adquery.ADQuery() 124 | 125 | q.execute_query( 126 | attributes = ["distinguishedName", "description", "cn"], 127 | where_clause=("cn = '*john*'"), 128 | ) 129 | 130 | for row in q.get_results(): 131 | pprint.pprint( row ) 132 | 133 | -------------------------------------------------------------------------------- /doc/test.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: pyad.adbase 2 | :members: 3 | 4 | .. automodule:: pyad.aduser 5 | :members: -------------------------------------------------------------------------------- /docs/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 2953a53581c40a741a5dca9afb6c7053 4 | tags: a205e9ed8462ae86fdd2f73488852ba9 5 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_modules/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Overview: module code — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |

All modules for which code is available

48 | 56 | 57 |
58 |
59 |
60 |
61 |
62 | 74 | 75 |
76 |
77 |
78 |
79 | 91 | 92 | 96 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/_modules/pyad/adcomputer.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pyad.adcomputer — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 43 | 44 |
45 |
46 |
47 |
48 | 49 |

Source code for pyad.adcomputer

 50 | from adobject import *
 51 | 
 52 | 
[docs]class ADComputer(ADObject): 53 | """""" 54 | @classmethod 55 |
[docs] def create(cls, name, container_object, enable=True, optional_attributes={}): 56 | assert type(name) == str 57 | assert container_object.__class__.__name__ == 'ADContainer' 58 | return container_object.create_computer(name=name,enable=enable,optional_attributes=optional_attributes) 59 |
60 |
[docs] def get_creator(self): 61 | """returns ADUser object of the user who added the computer to the domain. Returns None if user no longer exists.""" 62 | try: 63 | sid = str(pyadutils.convert_sid(self.get_attribute('mS-DS-CreatorSID', False))).split(':')[1] 64 | dn = adsearch.by_sid(sid) 65 | return ADUser(dn) 66 | except: 67 | return None 68 |
69 | ADObject._py_ad_object_mappings['computer'] = ADComputer 70 |
71 | 72 |
73 |
74 |
75 |
76 |
77 | 89 | 90 |
91 |
92 |
93 |
94 | 107 | 108 | 112 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/_modules/pyad/addomain.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pyad.addomain — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 43 | 44 |
45 |
46 |
47 |
48 | 49 |

Source code for pyad.addomain

 50 | from adcontainer import *
 51 | 
 52 | 
[docs]class ADDomain(ADContainer): 53 | 54 |
[docs] def get_default_upn(self): 55 | """Returns the default userPrincipalName for the domain.""" 56 | self._ldap_adsi_obj.GetInfoEx(["canonicalName",],0) 57 | return self._ldap_adsi_obj.get("canonicalName").rstrip('/') 58 |
59 | def __get_domain(self): 60 | if self._domain_pyad_obj is None: 61 | domain_path = 'dc=' + self.dn.lower().split("dc=",1)[1] 62 | self._domain_pyad_obj = ADDomain.from_dn(domain_path, 63 | options={'server':self.default_ldap_server,'port':self.default_ldap_port}) 64 | return self._domain_pyad_obj 65 | ADObject.get_domain = __get_domain 66 | 67 | ADObject._py_ad_object_mappings['domain'] = ADDomain 68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 87 | 88 |
89 |
90 |
91 |
92 | 105 | 106 | 110 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. Python Active Directory Tools (pyad) documentation master file, created by 2 | sphinx-quickstart on Sat Jul 6 13:48:15 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Python Active Directory Tools's Documentation 7 | ==================================================== 8 | 9 | pyad is a python library designed to provide a simple, object oriented interface to Active Directory through ADSI on the Windows platform. The code for this project is maintained at https://github.com/zakird/pyad and can be downloaded at https://pypi.python.org/pypi/pyad. 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | 15 | pyad 16 | 17 | objects 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | 27 | Requirements 28 | ============ 29 | 30 | pyad requires pywin32, which is available at http://sourceforge.net/projects/pywin32. 31 | 32 | License 33 | ======= 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. -------------------------------------------------------------------------------- /docs/_sources/objects.txt: -------------------------------------------------------------------------------- 1 | Manipulating Active Directory Objects 2 | ===================================== 3 | 4 | This page contains detailed information about how to manipulate various types of Active Directory objects. Keep in mind that all objects subclass ADObject. 5 | 6 | ADObject 7 | --------- 8 | 9 | .. autoclass:: pyad.adobject.ADObject 10 | :members: 11 | 12 | 13 | ADUser 14 | ---------------- 15 | 16 | .. autoclass:: pyad.aduser.ADUser 17 | :members: 18 | 19 | ADComputer 20 | ------------- 21 | 22 | .. autoclass:: pyad.adcomputer.ADComputer 23 | :members: 24 | 25 | ADGroup 26 | ------- 27 | 28 | .. autoclass:: pyad.adgroup.ADGroup 29 | :members: 30 | 31 | ADContainer 32 | ------------------ 33 | 34 | .. autoclass:: pyad.adcontainer.ADContainer 35 | :members: 36 | 37 | ADDomain 38 | -------- 39 | 40 | NOTE: ADDomain subclasses ADContainer. 41 | 42 | .. autoclass:: pyad.addomain.ADDomain 43 | :members: -------------------------------------------------------------------------------- /docs/_sources/pyad.txt: -------------------------------------------------------------------------------- 1 | Active Directory Basics 2 | ======================= 3 | 4 | Pyad is designed to expose the ADSI interface to Microsoft Active Directory in a straight-forward Pythonic manner. The library is designed to run on Windows. This page describes the basics of how to use the library. It does not, however, comprehensively describe the functionality of the library, which is more aptly documented in the next section. 5 | 6 | 7 | Connecting to Active Directory 8 | ------------------------------ 9 | 10 | By default, pyad will connect to the Active Directory domain to which the machine is joined (rootDSE):: 11 | 12 | from pyad import aduser 13 | user = aduser.ADUser.from_cn("myuser") 14 | 15 | However, it is possible to connect to a specific domain controller or to use alternate credentials, by calling pyad.set_defaults() or by passing in connection information in the options dictionary for each object you connect to. Authentication is performed over a secured connection, pyad will not pass credentials over clear text. The following options can be set in the `set_defaults` call: `ldap_server`, `gc_server`, `ldap_port`, `gc_port`, `username`, `password`, and `ssl` (True/False). For example, the following code will set the default connection parameters for all objects accessed through pyad:: 16 | 17 | from pyad import * 18 | pyad.set_defaults(ldap_server="dc1.domain.com", username="service_account", password="mypassword") 19 | user = pyad.aduser.ADUser.from_cn("myuser") 20 | 21 | It is also possible to pass in options when connecting to a specific object. This will not set the library defaults, but these settings will be used from any objects you derive from it (e.g. if you request group membership of a user) Example:: 22 | 23 | from pyad import aduser 24 | user = aduser.ADUser.from_cn("myuser", options=dict(ldap_server="dc1.domain.com")) 25 | 26 | Basic Object Manipulation 27 | ------------------------- 28 | 29 | There are first order Python classes for different types of objects in Active Directory. For example, ADUser represents user objects and ADGroup represents groups. All objects subclass ADObject. Most methods are defined in ADObject, but subclasses generally provide additional helper methods (e.g. ADUser has `set_password` and ADGroup has `add_member`). 30 | 31 | It is possible to connect to an object by distinguished name, CN, UPN, and GUID if you already know the type of object. Examples:: 32 | 33 | from pyad import aduser 34 | user1 = aduser.ADUser.from_dn("cn=myuser, ou=staff, dc=domain, dc=com") 35 | user2 = aduser.ADUser.from_cn("myuser") 36 | user3 = aduser.ADUser.from_guid("XXX-XXX-XXX") 37 | 38 | It is also possible to use the pyad factory with an arbitrary Active Directory object and to receive an appropriately classed Python object:: 39 | 40 | from pyad import pyad 41 | user = pyad.from_cn("user1") 42 | computer = pyad.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") 43 | group = pyad.from_guid("XXX-XXX-XXX") 44 | 45 | Unlike the ADSI interface, pyad objects are intended to interact with one another. Instead of adding the DN of a user to the members attribute of a group to add the user, you instead add the user object to the group. For instance:: 46 | 47 | user1 = ADUser.from_cn("myuser1") 48 | user2 = ADUser.from_cn("myuser2") 49 | group = ADGroup.from_dn("staff") 50 | 51 | group.add_members([user1, user2]) 52 | 53 | for user in group.get_members(): 54 | print user1.description 55 | 56 | However, it is still possible to directly manipulate any attribute outside of the helper methods that pyad provides:: 57 | 58 | user1 = ADUser.from_cn("myuser1") 59 | user.set_attribute("description", "new description") 60 | user.append_to_attribute("member", "cn=myuser1, ou=staff, dc=domain, dc=com") 61 | 62 | 63 | More details on how to manipulate the objects you find to is found in the next section. 64 | 65 | 66 | 67 | Creating, Moving, and Deleting Objects 68 | ---------------------------------- 69 | 70 | There are two methodologies for creating and deleting objects. In both cases, you must first bind to the parent container. When creating a new object, several attributes are required, but other additional attributes can be specified with the `optional_attributes` parameter. Example 1:: 71 | 72 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 73 | 74 | # create a new group without any optional attributes 75 | new_computer = ADComputer.create("WS-489", ou) 76 | 77 | # create a new group with additional attributes 78 | new_group = ADGroup.create("IT-STAFF", security_enabled=True, scope='UNIVERSAL', 79 | optional_attributes = {"description":"all IT staff in our company"}) 80 | 81 | It is also possible to create new objects from the parent container:: 82 | 83 | ou = ADContainer.from_dn("ou=workstations, dc=domain, dc=com") 84 | computer = ou.create_computer("WS-490") 85 | 86 | Once objects are created, they can be moved:: 87 | 88 | computer = ADComputer.from_cn("WS-500") 89 | computer.move(ADContainer.from_dn("ou=workstations, ou=HR, dc=company, dc=com")) 90 | 91 | or renamed:: 92 | 93 | computer = ADComputer.from_cn("WS-500") 94 | computer.rename("WS-501") 95 | 96 | Objects can be removed by calling delete():: 97 | 98 | ADComputer.from_cn("WS-500").delete() 99 | 100 | 101 | Searching Active Directory 102 | -------------------------- 103 | 104 | As shown above, objects can be directly connected to via CN, DN, GUID, or UPN. However, objects can also be searched for through the ADQuery interface (and in the background, this is how objects are actually found when you connect by CN). It is important to note that the ADQuery interface will not provide you with pyad objects, but instead with only the attributes for which you queried, for performance reasons. Example:: 105 | 106 | import pyad.adquery 107 | q = pyad.adquery.ADQuery() 108 | 109 | q.execute_query( 110 | attributes = ["distinguishedName", "description"], 111 | where_clause = "objectClass = '*'", 112 | base_dn = "OU=users, DC=domain, DC=com" 113 | ) 114 | 115 | for row in q.get_results(): 116 | print row["distinguishedName"] 117 | -------------------------------------------------------------------------------- /docs/_sources/test.txt: -------------------------------------------------------------------------------- 1 | .. automodule:: pyad.adbase 2 | :members: 3 | 4 | .. automodule:: pyad.aduser 5 | :members: -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | window.setTimeout(function() { 172 | $.each(terms, function() { 173 | body.highlightText(this.toLowerCase(), 'highlighted'); 174 | }); 175 | }, 10); 176 | $('') 178 | .appendTo($('#searchbox')); 179 | } 180 | }, 181 | 182 | /** 183 | * init the domain index toggle buttons 184 | */ 185 | initIndexTable : function() { 186 | var togglers = $('img.toggler').click(function() { 187 | var src = $(this).attr('src'); 188 | var idnum = $(this).attr('id').substr(7); 189 | $('tr.cg-' + idnum).toggle(); 190 | if (src.substr(-9) == 'minus.png') 191 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 192 | else 193 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 194 | }).css('display', ''); 195 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 196 | togglers.click(); 197 | } 198 | }, 199 | 200 | /** 201 | * helper function to hide the search marks again 202 | */ 203 | hideSearchWords : function() { 204 | $('#searchbox .highlight-link').fadeOut(300); 205 | $('span.highlighted').removeClass('highlighted'); 206 | }, 207 | 208 | /** 209 | * make the url absolute 210 | */ 211 | makeURL : function(relativeURL) { 212 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 213 | }, 214 | 215 | /** 216 | * get the current relative url 217 | */ 218 | getCurrentURL : function() { 219 | var path = document.location.pathname; 220 | var parts = path.split(/\//); 221 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 222 | if (this == '..') 223 | parts.pop(); 224 | }); 225 | var url = parts.join('/'); 226 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 227 | } 228 | }; 229 | 230 | // quick alias for translations 231 | _ = Documentation.gettext; 232 | 233 | $(document).ready(function() { 234 | Documentation.init(); 235 | }); 236 | -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakird/pyad/54517c575f2531941bb5889899c9424a17072c98/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k 3 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 |

Python Module Index

52 | 53 |
54 | p 55 |
56 | 57 | 58 | 59 | 61 | 62 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 77 |
 
60 | p
65 | pyad 66 |
    70 | pyad.adbase 71 |
    75 | pyad.aduser 76 |
78 | 79 | 80 |
81 |
82 |
83 |
84 |
85 | 97 | 98 |
99 |
100 |
101 |
102 | 114 | 115 | 119 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 101 | 105 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /docs/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({envversion:42,terms:{all:[0,2,3],code:[1,3],queri:[0,2,3],get_creat:2,prefix:2,create_us:2,force_pwd_change_on_login:2,follow:3,children:2,row:3,get_group_scop:2,unnumb:[],remove_all_memb:2,readabl:2,ws1:3,under:1,mycomput:2,aspx:2,sourc:[0,2],string:2,fals:[2,3],set_group_typ:2,util:0,govern:1,join:3,ldap_serv:3,upn:3,list:2,iter:2,add_memb:[2,3],item:[],adjust:[],impli:1,myuser:3,aptli:3,sourceforg:1,design:[1,3],pass:3,download:1,further:2,append:2,index:1,from_guid:[2,3],section:3,current:2,delet:[],version:1,"new":[2,3],net:1,method:3,create_group:2,deriv:3,gener:[2,3],path:[],valu:2,search:[],set_group_scop:2,datetim:2,chang:2,via:3,ads_user_flag:2,ldap_port:3,modul:1,"boolean":2,unit:2,from:[2,3],describ:3,univers:[2,3],two:3,next:[2,3],live:2,call:3,scope:[2,3],type:[2,3],more:[2,3],ital:[],rootds:3,particular:2,known:2,compani:3,must:[2,3],none:2,word:[],paragraph:[],can:[1,2,3],control:[2,3],mycontain:2,give:[],sudo:[],accept:2,remove_child:2,remove_memb:2,parent_container_path:2,multipl:2,secur:[2,3],anoth:3,from_com_object:2,write:1,how:[2,3],sid:2,instead:[2,3],simpl:1,updat:2,adcontain:[],msdn:2,set_samaccountnam:2,membership:[2,3],new_typ:2,mai:[1,2],law:1,github:1,classmethod:2,bind:3,create_contain:2,credenti:3,element:2,execute_queri:3,maintain:1,allow:2,order:3,talk:[],help:0,get_mandatory_attribut:2,dump_to_xml:2,get_domain:2,through:[1,2,3],adus:[],still:3,paramet:3,getattribut:2,group:[2,3],valuestoremov:2,platform:1,window:[1,3],comprehens:3,filter_:2,main:[],easier:2,set_managedbi:2,them:2,"return":2,thei:[2,3],name:[2,3],authent:3,service_account:3,each:3,found:[2,3],domain:[2,3],zakird:1,connect:[],our:3,orient:1,shown:3,agre:1,content:1,print:3,factori:3,manipul:[],differ:3,reason:3,base:[0,2],dictionari:[2,3],org:1,workstat:3,basi:1,synchron:2,keep:2,length:[],outsid:3,organiz:2,first:3,softwar:1,directli:3,onc:3,number:[],date:2,unlik:3,alreadi:3,get_group_typ:2,new_comput:3,given:2,interact:3,least:[],similarli:2,store:[0,2],schema:2,option:[2,3],methodolog:3,adjust_pyad_typ:[],from_dn:[2,3],specifi:[2,3],myuser2:3,myuser1:3,kind:1,sync_membership:2,keyword:2,provid:[1,3],remov:[2,3],project:1,adbas:0,parent_contain:2,userprincipalnam:2,comput:[2,3],behavior:2,mind:2,argument:2,packag:0,blacklist_attribut:2,expir:2,have:[],need:2,is_member_of:2,inform:[2,3],no_flush:2,note:[2,3],also:3,without:[1,3],which:[1,2,3],guid_str:2,singl:2,copi:1,unless:1,distribut:[1,2],usernam:3,who:2,most:[2,3],automodul:[],prefixed_cn:2,"class":[0,2,3],addomain:[],renam:[2,3],set_user_account_control_set:2,objectclass:3,clear:[2,3],request:3,doe:[2,3],determin:2,get_password_last_set:2,microsoft:[2,3],set_expir:2,text:3,permiss:1,new_ou_object:2,find:3,staff:3,xml:2,access:3,onli:[2,3],nobodi:2,whitelist_attribut:2,transact:2,apach:1,should:2,from_cn:3,dict:3,ldap:2,local:2,over:[2,3],adcomput:[],move:[],variou:2,get:2,express:1,pypi:1,com_object:2,obtain:1,ssl:3,requir:[],bar:[],enabl:2,userflag:2,useraccountcontrol:2,whether:2,contain:[2,3],where:2,usnchang:2,set:[2,3],dump:2,base_dn:3,mutabl:2,see:[1,2],mandatori:2,valuestoappend:2,managedbi:2,kei:2,"import":3,attribut:[2,3],altern:3,check_memb:2,distinguish:[2,3],addit:3,both:3,last:2,pyhon:[],howev:3,instanc:3,login:2,com:[1,2,3],distinguished_nam:2,guid:[2,3],empti:2,basic:[],getex:2,new_scop:2,xxx:3,get_result:3,ani:[1,2,3],child:2,new_popul:2,"case":[2,3],multi:2,ident:2,straight:3,remove_from_attribut:2,defin:[2,3],abov:3,manner:3,get_attribut:2,helper:3,itself:[],aa772300:2,sever:3,parent:[2,3],adgroup:[],perform:3,remove_from_group:2,belong:2,same:2,member:[2,3],new_group:3,http:[1,2],adqueri:3,gc_port:3,nest:2,wherea:2,get_user_account_control_set:2,user:[2,3],rremov:2,travers:2,appropri:3,entri:2,get_allowed_attribut:2,always_return_list:2,exampl:[],command:[],container_object:2,undefin:2,set_attribut:3,just:2,mypassword:3,distinguishednam:[2,3],pywin32:1,optional_attribut:[2,3],human:2,languag:1,expos:3,upn_suffix:2,set_password:[2,3],get_children:2,add:[2,3],match:2,applic:1,update_attribut:2,know:3,background:3,password:[2,3],recurs:2,clear_attribut:2,like:[],specif:[1,3],arbitrari:3,safest:2,pyad:[],either:[1,2],page:[1,2,3],create_comput:[2,3],www:1,ignoregroup:2,back:[],global:2,sampl:[],integ:2,adobject:[],get_memb:[2,3],guarante:2,librari:[1,2,3],add_to_group:2,insensit:2,get_default_upn:2,adspath:2,subclass:[2,3],adsi_ldap_com_object:2,leav:2,condit:1,foo:[],machin:3,object:[],run:3,bold:[],get_usnchang:2,attribute_value_dict:2,"super":[],about:2,actual:3,append_to_attribut:[2,3],choke:2,disabl:2,emphasi:[],within:[0,2],automat:[],warranti:1,check_contains_memb:2,avail:1,get_memberof:2,interfac:[1,3],forward:3,"function":3,forc:2,regard:2,get_optional_attribut:2,"true":[2,3],reset:2,possibl:3,new_nam:2,limit:1,otherwis:2,similar:2,expect:2,user2:3,user3:3,creat:[],user1:3,repres:[2,3],exist:2,check:2,adsi:[1,2,3],quot:[],when:[2,3],detail:[2,3],power:[],"default":[0,2,3],other:3,set_default:3,you:[1,2,3],dc1:3,intend:3,newvalu:2,security_en:[2,3],experienc:2,receiv:3,longer:2,descript:3,gc_server:3,where_claus:3,ignor:2,clear_managedbi:2,potenti:2,time:2,organizationalunit:2},objtypes:{"0":"py:module","1":"py:method","2":"py:class","3":"py:classmethod","4":"py:attribute"},objnames:{"0":["py","module","Python module"],"1":["py","method","Python method"],"2":["py","class","Python class"],"3":["py","classmethod","Python class method"],"4":["py","attribute","Python attribute"]},filenames:["test","index","objects","pyad"],titles:["<no title>","Python Active Directory Tools’s Documentation","Manipulating Active Directory Objects","Active Directory Basics"],objects:{"pyad.addomain.ADDomain":{get_default_upn:[2,1,1,""]},"pyad.adobject":{ADObject:[2,2,1,""]},"pyad.adobject.ADObject":{parent_container:[2,4,1,""],rename:[2,1,1,""],dump_to_xml:[2,1,1,""],get_domain:[2,1,1,""],set_user_account_control_setting:[2,1,1,""],is_member_of:[2,1,1,""],parent_container_path:[2,4,1,""],move:[2,1,1,""],guid:[2,4,1,""],update_attributes:[2,1,1,""],get_user_account_control_settings:[2,1,1,""],remove_from_group:[2,1,1,""],from_com_object:[2,3,1,""],sid:[2,4,1,""],get_mandatory_attributes:[2,1,1,""],add_to_group:[2,1,1,""],type:[2,4,1,""],dn:[2,4,1,""],enable:[2,1,1,""],get_attribute:[2,1,1,""],adsPath:[2,4,1,""],guid_str:[2,4,1,""],set_managedby:[2,1,1,""],update_attribute:[2,1,1,""],disable:[2,1,1,""],get_allowed_attributes:[2,1,1,""],clear_attribute:[2,1,1,""],prefixed_cn:[2,4,1,""],remove_from_attribute:[2,1,1,""],from_guid:[2,3,1,""],from_dn:[2,3,1,""],get_uSNChanged:[2,1,1,""],get_memberOfs:[2,1,1,""],clear_managedby:[2,1,1,""],get_optional_attributes:[2,1,1,""],append_to_attribute:[2,1,1,""],"delete":[2,1,1,""]},"pyad.adcontainer":{ADContainer:[2,2,1,""]},"pyad.adgroup":{ADGroup:[2,2,1,""]},pyad:{aduser:[0,0,1,""],adbase:[0,0,1,""]},"pyad.aduser":{ADUser:[2,2,1,""]},"pyad.adcomputer.ADComputer":{create:[2,3,1,""],get_creator:[2,1,1,""]},"pyad.adbase":{ADBase:[0,2,1,""]},"pyad.addomain":{ADDomain:[2,2,1,""]},"pyad.adcomputer":{ADComputer:[2,2,1,""]},"pyad.aduser.ADUser":{create:[2,3,1,""],get_password_last_set:[2,1,1,""],set_password:[2,1,1,""],set_expiration:[2,1,1,""],force_pwd_change_on_login:[2,1,1,""]},"pyad.adcontainer.ADContainer":{create_group:[2,1,1,""],get_children:[2,1,1,""],create_user:[2,1,1,""],remove_child:[2,1,1,""],create_computer:[2,1,1,""],create_container:[2,1,1,""]},"pyad.adgroup.ADGroup":{sync_membership:[2,1,1,""],check_contains_member:[2,1,1,""],get_group_type:[2,1,1,""],create:[2,3,1,""],get_group_scope:[2,1,1,""],set_group_type:[2,1,1,""],remove_all_members:[2,1,1,""],add_members:[2,1,1,""],set_group_scope:[2,1,1,""],remove_members:[2,1,1,""],get_members:[2,1,1,""]}},titleterms:{adgroup:2,creat:3,tabl:1,indic:1,connect:3,adobject:2,adus:2,subject:[],adcontain:2,titl:0,licens:1,activ:[1,2,3],delet:3,basic:3,document:1,adcomput:2,manipul:[2,3],python:1,tool:1,object:[2,3],inlin:[],requir:1,search:3,addomain:2,subtitl:[],move:3,directori:[1,2,3],markup:[],pyad:[],exampl:[],thi:[]}}) -------------------------------------------------------------------------------- /docs/test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <no title> — Python Active Directory Tools 0.5.03 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 | class pyad.adbase.ADBase[source]
50 |

Base class that is utilized by all objects within package to help 51 | store defaults. (search, query, all AD objects)

52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 |
60 |
61 |

This Page

62 | 66 | 78 | 79 |
80 |
81 |
82 |
83 | 95 | 96 | 100 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /pyad/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adbase import set_defaults as pyad_setdefaults 3 | __all__ = ["adbase", "adquery", "adsearch", "adobject", "adcomputer", "adcontainer", "addomain", "adgroup", "aduser", "pyad"] 4 | -------------------------------------------------------------------------------- /pyad/adbase.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from builtins import object 4 | import sys 5 | import datetime 6 | import time 7 | import types 8 | import xml.dom.minidom as xml 9 | 10 | # Since we're depending on ADSI, you have to be on windows... 11 | if sys.platform != 'win32': 12 | raise Exception("Must be running Windows in order to use pyad.") 13 | 14 | try: 15 | import win32api 16 | import pywintypes 17 | import win32com.client 18 | import win32security 19 | except ImportError: 20 | raise Exception("pywin32 library required. Download from http://sourceforge.net/projects/pywin32/") 21 | 22 | # Import constants and other common elements. 23 | from .pyadconstants import * 24 | from .pyadexceptions import * 25 | 26 | _adsi_provider = win32com.client.Dispatch('ADsNameSpaces') 27 | 28 | try: 29 | # Discover default domain and forest information 30 | __default_domain_obj = _adsi_provider.GetObject('', "LDAP://rootDSE") 31 | except: 32 | # If there was an error, this this computer might not be on a domain. 33 | print("WARN: unable to connect to default domain. Computer is likely not attached to an AD domain") 34 | __default_domain_obj = None 35 | _default_detected_forest = None 36 | _default_detected_domain = None 37 | else: 38 | # connecting to rootDSE will connect to the domain that the 39 | # current logged-in user belongs to.. which is generally the 40 | # domain under question and therefore becomes the default domain. 41 | _default_detected_forest = __default_domain_obj.Get("rootDomainNamingContext") 42 | _default_detected_domain = __default_domain_obj.Get("defaultNamingContext") 43 | 44 | 45 | class ADBase(object): 46 | """Base class that is utilized by all objects within package to help 47 | store defaults. (search, query, all AD objects)""" 48 | 49 | DEFAULTS_OPTIONS_MAPPINGS = [ 50 | ("default_ldap_server", "server"), 51 | ("default_gc_server", "gc_server"), 52 | ("default_ldap_port", "port"), 53 | ("default_gc_port", "gc_port"), 54 | ("default_username", "username"), 55 | ("default_password", "password"), 56 | ("default_ldap_authentication_flag", "authentication_flag"), 57 | ("default_ssl", "ssl") 58 | ] 59 | 60 | default_ssl = False 61 | default_ldap_server = None 62 | default_gc_server = None 63 | default_ldap_port = None 64 | default_gc_port = None 65 | default_username = None 66 | default_password = None 67 | default_ldap_protocol = 'LDAP' 68 | default_ldap_authentication_flag = 0 # No credentials 69 | default_domain = _default_detected_domain 70 | default_forest = _default_detected_forest 71 | adsi_provider = _adsi_provider 72 | 73 | def _set_defaults(self, options): 74 | for (default, key) in ADBase.DEFAULTS_OPTIONS_MAPPINGS: 75 | if key in options: 76 | setattr(self, default, options[key]) 77 | 78 | def _make_options(self): 79 | options = dict() 80 | for (default, key) in ADBase.DEFAULTS_OPTIONS_MAPPINGS: 81 | val = getattr(self, default) 82 | if val: 83 | options[key] = val 84 | return options 85 | 86 | @property 87 | def _safe_default_domain(self): 88 | if self.default_domain: 89 | return self.default_domain 90 | raise Exception("Unable to detect default domain. Must specify search base.") 91 | 92 | @property 93 | def _safe_default_forest(self): 94 | if self.default_forest: 95 | return self.default_forest 96 | raise Exception("Unable to detect default forest. Must specify search base.") 97 | 98 | def set_defaults(**kwargs): 99 | for k, v in kwargs.items(): 100 | setattr(ADBase, '_'.join(('default', k)), v) 101 | -------------------------------------------------------------------------------- /pyad/adcomputer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from builtins import str 3 | from .adobject import * 4 | 5 | class ADComputer(ADObject): 6 | """Python class representing a computer object in Active Directory.""" 7 | @classmethod 8 | def create(cls, name, container_object, enable=True, optional_attributes={}): 9 | """Creates and returns a new computer object.""" 10 | assert type(name) == str 11 | assert container_object.__class__.__name__ == 'ADContainer' 12 | return container_object.create_computer(name=name,enable=enable,optional_attributes=optional_attributes) 13 | 14 | def get_creator(self): 15 | """returns ADUser object of the user who added the computer to the domain. Returns None if user no longer exists.""" 16 | try: 17 | sid = str(pyadutils.convert_sid(self.get_attribute('mS-DS-CreatorSID', False))).split(':')[1] 18 | dn = adsearch.by_sid(sid) 19 | return ADUser(dn) 20 | except: 21 | return None 22 | 23 | ADObject._py_ad_object_mappings['computer'] = ADComputer 24 | -------------------------------------------------------------------------------- /pyad/adcontainer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adobject import * 3 | from .aduser import ADUser 4 | from .adcomputer import ADComputer 5 | from .adgroup import ADGroup 6 | from . import pyadconstants 7 | 8 | class ADContainer(ADObject): 9 | def get_children_iter(self, recursive=False, filter_=None): 10 | for com_object in self._ldap_adsi_obj: 11 | q = ADObject.from_com_object(com_object) 12 | q.adjust_pyad_type() 13 | if q.type == 'organizationalUnit' and recursive: 14 | for c in q.get_children_iter(recursive=recursive): 15 | if not filter_ or c.__class__ in filter_: 16 | yield c 17 | if not filter_ or q.__class__ in filter_: 18 | yield q 19 | 20 | def get_children(self, recursive=False, filter_=None): 21 | "Iterate over the children objects in the container." 22 | return list(self.get_children_iter(recursive=recursive, filter_=filter_)) 23 | 24 | def __create_object(self, type_, name): 25 | prefix = 'ou' if type_ == 'organizationalUnit' else 'cn' 26 | prefixed_name = '='.join((prefix,name)) 27 | return self._ldap_adsi_obj.Create(type_, prefixed_name) 28 | 29 | def create_user(self, name, password=None, upn_suffix=None, enable=True,optional_attributes={}): 30 | """Create a new user object in the container""" 31 | try: 32 | if not upn_suffix: 33 | upn_suffix = self.get_domain().get_default_upn() 34 | upn = '@'.join((name, upn_suffix)) 35 | obj = self.__create_object('user', name) 36 | obj.Put('sAMAccountName', optional_attributes.get('sAMAccountName', name)) 37 | obj.Put('userPrincipalName', upn) 38 | obj.SetInfo() 39 | pyadobj = ADUser.from_com_object(obj) 40 | if enable: 41 | pyadobj.enable() 42 | if password: 43 | pyadobj.set_password(password) 44 | pyadobj.update_attributes(optional_attributes) 45 | return pyadobj 46 | except pywintypes.com_error as e: 47 | pyadutils.pass_up_com_exception(e) 48 | 49 | def create_group(self, name, security_enabled=True, scope='GLOBAL', optional_attributes={}): 50 | """Create a new group object in the container""" 51 | try: 52 | obj = self.__create_object('group', name) 53 | obj.Put('sAMAccountName',name) 54 | val = pyadconstants.ADS_GROUP_TYPE[scope] 55 | if security_enabled: 56 | val = val | pyadconstants.ADS_GROUP_TYPE['SECURITY_ENABLED'] 57 | obj.Put('groupType',val) 58 | obj.SetInfo() 59 | pyadobj = ADGroup.from_com_object(obj) 60 | pyadobj.update_attributes(optional_attributes) 61 | return pyadobj 62 | except pywintypes.com_error as e: 63 | pyadutils.pass_up_com_exception(e) 64 | 65 | def create_container(self, name, optional_attributes={}): 66 | """Create a new organizational unit in the container""" 67 | try: 68 | obj = self.__create_object('organizationalUnit', name) 69 | obj.SetInfo() 70 | pyadobj = ADContainer.from_com_object(obj) 71 | pyadobj.update_attributes(optional_attributes) 72 | return pyadobj 73 | except pywintypes.com_error as e: 74 | pyadutils.pass_up_com_exception(e) 75 | 76 | def create_computer(self, name, enable=True,optional_attributes={}): 77 | """Create a new computer object in the container""" 78 | try: 79 | obj = self.__create_object('computer', name) 80 | obj.Put('sAMAccountName', name + '$') 81 | if enable: 82 | obj.Put('userAccountControl', 4128) 83 | else: 84 | obj.Put('userAccountControl', 4130) 85 | obj.SetInfo() 86 | pyadobj = ADComputer.from_com_object(obj) 87 | if enable: 88 | pyadobj.enable() 89 | pyadobj.update_attributes(optional_attributes) 90 | return pyadobj 91 | except pywintypes.com_error as e: 92 | pyadutils.pass_up_com_exception(e) 93 | 94 | def remove_child(self, child): 95 | """Rremoves the child object from the domain""" 96 | self._ldap_adsi_obj.Delete(child.type, child.prefixed_cn) 97 | 98 | ADObject._py_ad_object_mappings['organizationalUnit'] = ADContainer 99 | ADObject._py_ad_object_mappings['container'] = ADContainer 100 | -------------------------------------------------------------------------------- /pyad/addomain.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adcontainer import * 3 | 4 | class ADDomain(ADContainer): 5 | 6 | def get_default_upn(self): 7 | """Returns the default userPrincipalName for the domain.""" 8 | self._ldap_adsi_obj.GetInfoEx(["canonicalName",],0) 9 | return self._ldap_adsi_obj.get("canonicalName").rstrip('/') 10 | 11 | def __get_domain(self): 12 | """Returns the domain to which the object belongs.""" 13 | if self._domain_pyad_obj is None: 14 | domain_path = 'dc=' + self.dn.lower().split("dc=",1)[1] 15 | self._domain_pyad_obj = ADDomain.from_dn(domain_path, 16 | options=self._make_options()) 17 | return self._domain_pyad_obj 18 | ADObject.get_domain = __get_domain 19 | 20 | ADObject._py_ad_object_mappings['domain'] = ADDomain 21 | -------------------------------------------------------------------------------- /pyad/adquery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adbase import * 3 | from . import pyadutils 4 | 5 | class ADQuery(ADBase): 6 | # Requests secure authentication. When this flag is set, 7 | # Active Directory will use Kerberos, and possibly NTLM, 8 | # to authenticate the client. 9 | ADS_SECURE_AUTHENTICATION = 1 10 | # Requires ADSI to use encryption for data 11 | # exchange over the network. 12 | ADS_USE_ENCRYPTION = 2 13 | 14 | # ADS_SCOPEENUM enumeration. Documented at http://goo.gl/83G1S 15 | 16 | # Searches the whole subtree, including all the 17 | # children and the base object itself. 18 | ADS_SCOPE_SUBTREE = 2 19 | # Searches one level of the immediate children, 20 | # excluding the base object. 21 | ADS_SCOPE_ONELEVEL = 1 22 | # Limits the search to the base object. 23 | # The result contains, at most, one object. 24 | ADS_SCOPE_BASE = 0 25 | 26 | # the methodology for performing a command with credentials 27 | # and for forcing encryption can be found at http://goo.gl/GGCK5 28 | 29 | def __init__(self, options={}): 30 | self.__adodb_conn = win32com.client.Dispatch("ADODB.Connection") 31 | if self.default_username and self.default_password: 32 | self.__adodb_conn.Provider = u"ADsDSOObject" 33 | self.__adodb_conn.Properties("User ID").Value = self.default_username 34 | self.__adodb_conn.Properties("Password").Value = self.default_password 35 | adsi_flag = ADQuery.ADS_SECURE_AUTHENTICATION | \ 36 | ADQuery.ADS_USE_ENCRYPTION 37 | self.__adodb_conn.Properties("ADSI Flag").Value = adsi_flag 38 | self.__adodb_conn.Properties("Encrypt Password").Value = True 39 | self.__adodb_conn.Open() 40 | else: 41 | self.__adodb_conn.Open("Provider=ADSDSOObject") 42 | 43 | self.reset() 44 | 45 | def reset(self): 46 | self.__rs = self.__rc = None 47 | self.__queried = False 48 | 49 | def execute_query(self, attributes=["distinguishedName"], where_clause=None, 50 | type="LDAP", base_dn=None, page_size=1000, 51 | search_scope="subtree", options={}, ldap_dialect=False): 52 | assert type in ("LDAP", "GC") 53 | if not base_dn: 54 | if type == "LDAP": 55 | base_dn = self._safe_default_domain 56 | if type == "GC": 57 | base_dn = self._safe_default_forest 58 | # https://docs.microsoft.com/en-us/windows/win32/adsi/searching-with-activex-data-objects-ado 59 | 60 | # Ldap dialect 61 | if ldap_dialect: 62 | query = f"<{pyadutils.generate_ads_path(base_dn, type, self.default_ldap_server, self.default_ldap_port)}>; {where_clause};{','.join(attributes)}" 63 | else: 64 | # SQL dialect 65 | query = "SELECT %s FROM '%s'" % (','.join(attributes), 66 | pyadutils.generate_ads_path(base_dn, type, 67 | self.default_ldap_server, self.default_ldap_port)) 68 | if where_clause: 69 | query = ' '.join((query, 'WHERE', where_clause)) 70 | 71 | command = win32com.client.Dispatch("ADODB.Command") 72 | command.ActiveConnection = self.__adodb_conn 73 | command.Properties("Page Size").Value = page_size 74 | if search_scope == "subtree": 75 | command.Properties("Searchscope").Value = ADQuery.ADS_SCOPE_SUBTREE 76 | elif search_scope == "onelevel": 77 | command.Properties("Searchscope").Value = ADQuery.ADS_SCOPE_ONELEVEL 78 | elif search_scope == "base": 79 | command.Properties("Searchscope").Value = ADQuery.ADS_SCOPE_BASE 80 | else: 81 | raise Exception("Unknown search_base %s, must be subtree, "\ 82 | "onelevel or base" % search_scope) 83 | command.CommandText = query 84 | self.__rs, self.__rc = command.Execute() 85 | self.__queried = True 86 | 87 | def get_row_count(self): 88 | return self.__rs.RecordCount 89 | 90 | def get_single_result(self): 91 | if self.get_row_count() != 1: 92 | raise invalidResults(self.get_row_count()) 93 | self.__rs.MoveFirst() 94 | d = {} 95 | for f in self.__rs.Fields: 96 | d[f.Name] = f.Value 97 | return d 98 | 99 | def get_results(self): 100 | if not self.__queried: 101 | raise noExecutedQuery 102 | if not self.__rs.EOF: 103 | self.__rs.MoveFirst() 104 | while not self.__rs.EOF: 105 | d = {} 106 | for f in self.__rs.Fields: 107 | d[f.Name] = f.Value 108 | yield d 109 | self.__rs.MoveNext() 110 | 111 | def get_all_results(self): 112 | if not self.__queried: 113 | raise noExecutedQuery 114 | l = [] 115 | for d in self.get_results(): 116 | l.append(d) 117 | return l 118 | -------------------------------------------------------------------------------- /pyad/adsearch.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adquery import * 3 | from .adbase import * 4 | 5 | _ad_query_obj = ADQuery() 6 | 7 | def by_cn(cn, search_base=None, options={}): 8 | if not search_base: 9 | if not ADBase.default_domain: 10 | raise Exception("Unable to detect default domain. Must specify search base.") 11 | search_base = ADBase.default_domain 12 | _ad_query_obj.reset() 13 | 14 | _ad_query_obj.execute_query(where_clause=("CN = '%s'" % cn), 15 | base_dn=search_base, 16 | options=options, 17 | type="GC") 18 | return _ad_query_obj.get_single_result()['distinguishedName'] 19 | 20 | def by_upn(upn, search_base=None, options={}): 21 | if not search_base: 22 | if not ADBase.default_forest: 23 | raise Exception("Unable to detect default forest. Must specify search base.") 24 | search_base = ADBase.default_forest 25 | _ad_query_obj.reset() 26 | _ad_query_obj.execute_query(where_clause=("userPrincipalName = '%s'" % upn), 27 | base_dn=search_base, 28 | type="GC", 29 | options=options) 30 | return _ad_query_obj.get_single_result()['distinguishedName'] 31 | 32 | def by_sid(sid, search_base=None, options={}): 33 | if not search_base: 34 | if not ADBase.default_domain: 35 | raise Exception("Unable to detect default domain. Must specify search base.") 36 | search_base = ADBase.default_domain 37 | _ad_query_obj.reset() 38 | _ad_query_obj.execute_query(where_clause=("objectSid = '%s'" % sid), 39 | base_dn=search_base, 40 | options=options, 41 | type="GC") 42 | return _ad_query_obj.get_single_result()['distinguishedName'] 43 | 44 | def all_results_by_cn(cn, search_base=None, options={}): 45 | if not search_base: 46 | if not ADBase.default_domain: 47 | raise Exception("Unable to detect default domain. Must specify search base.") 48 | search_base = ADBase.default_domain 49 | _ad_query_obj.reset() 50 | 51 | _ad_query_obj.execute_query(where_clause=("CN = '%s'" % cn), 52 | base_dn=search_base, 53 | options=options, 54 | type="GC") 55 | return [result['distinguishedName'] for result in _ad_query_obj.get_all_results()] 56 | 57 | def all_results_by_upn(upn, search_base=None, options={}): 58 | if not search_base: 59 | if not ADBase.default_forest: 60 | raise Exception("Unable to detect default forest. Must specify search base.") 61 | search_base = ADBase.default_forest 62 | _ad_query_obj.reset() 63 | _ad_query_obj.execute_query(where_clause=("userPrincipalName = '%s'" % upn), 64 | base_dn=search_base, 65 | type="GC", 66 | options=options) 67 | return [result['distinguishedName'] for result in _ad_query_obj.get_all_results()] 68 | 69 | def all_results_by_sid(sid, search_base=None, options={}): 70 | if not search_base: 71 | if not ADBase.default_domain: 72 | raise Exception("Unable to detect default domain. Must specify search base.") 73 | search_base = ADBase.default_domain 74 | _ad_query_obj.reset() 75 | _ad_query_obj.execute_query(where_clause=("objectSid = '%s'" % sid), 76 | base_dn=search_base, 77 | options=options, 78 | type="GC") 79 | return [result['distinguishedName'] for result in _ad_query_obj.get_all_results()] -------------------------------------------------------------------------------- /pyad/aduser.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adobject import * 3 | from .adsearch import _ad_query_obj 4 | import datetime 5 | 6 | class ADUser(ADObject): 7 | 8 | @classmethod 9 | def create(cls, name, container_object, password=None, upn_suffix=None, 10 | enable=True, optional_attributes={}): 11 | """Creates and returns a new active directory user""" 12 | return container_object.create_user( 13 | name=name, 14 | password=password, 15 | upn_suffix=upn_suffix, 16 | enable=enable, 17 | optional_attributes=optional_attributes 18 | ) 19 | 20 | def set_password(self, password): 21 | """Sets the users password""" 22 | try: 23 | self._ldap_adsi_obj.SetPassword(password) 24 | self._flush() 25 | except pywintypes.com_error as excpt: 26 | pyadutils.pass_up_com_exception(excpt) 27 | 28 | def force_pwd_change_on_login(self): 29 | """Forces the user to change their password the next time they login""" 30 | self.update_attribute('PwdLastSet',0) 31 | 32 | def grant_password_lease(self): 33 | self.update_attribute('PwdLastSet',-1) 34 | 35 | def get_password_last_set(self): 36 | """Returns datetime object of when user last reset their password.""" 37 | return self._get_password_last_set() 38 | 39 | def get_max_pwd_age(self): 40 | """Returns timespan object representing the max password age on a user's domain""" 41 | return pyadutils.convert_timespan(self.get_domain().maxPwdAge) 42 | 43 | def get_expiration(self): 44 | """Gets the expiration date of the password as a datetime object. 45 | The _ldap_adsi_obj.AccountExpirationDate can be inaccurate and 46 | return the UNIX Epoch instead of the true expiration date.""" 47 | uac_settings = self.get_user_account_control_settings() 48 | if any(uac_settings[flag] for flag in ["SMARTCARD_REQUIRED", 49 | "DONT_EXPIRE_PASSWD", "WORKSTATION_TRUST_ACCOUNT", 50 | "SERVER_TRUST_ACCOUNT", "INTERDOMAIN_TRUST_ACCOUNT"]): 51 | return None 52 | elif self.get_attribute('pwdLastSet', False) is None: 53 | return datetime.datetime(1970,1,1) 54 | else: 55 | return self.get_password_last_set() + self.get_max_pwd_age() 56 | 57 | def set_expiration(self, dt): 58 | """Sets the expiration date of the password to the given value""" 59 | self._ldap_adsi_obj.AccountExpirationDate = dt 60 | self._flush() 61 | 62 | def get_password_expired(self): 63 | """Returns a bool representing whether the password has expired. 64 | The passwordexpired property will often return True even if not expired.""" 65 | expiration_date = self.get_expiration() 66 | if expiration_date is None: 67 | return False 68 | return expiration_date < datetime.datetime.now() 69 | 70 | def unlock(self): 71 | """Unlock the user's account""" 72 | self.update_attribute('lockoutTime',0) 73 | 74 | ADObject._py_ad_object_mappings['user'] = ADUser 75 | -------------------------------------------------------------------------------- /pyad/pyad.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .adobject import * 3 | from .pyadexceptions import InvalidObjectException, invalidResults 4 | from . import aduser, adcomputer, addomain, addomain, adgroup, adobject, pyadconstants, adcontainer 5 | 6 | def from_cn(common_name, search_base=None, options={}): 7 | Escape = {"\\":"\\5C","*":"\\2A","(":"\\28",")":"\\29"} 8 | common_name = "".join([Escape.get(char, char) for char in common_name]) 9 | try: 10 | q = ADObject.from_cn(common_name, search_base, options) 11 | q.adjust_pyad_type() 12 | return q 13 | except invalidResults: 14 | return None 15 | 16 | def from_dn(distinguished_name, options={}): 17 | try: 18 | q = ADObject.from_dn(distinguished_name,options) 19 | q.adjust_pyad_type() 20 | return q 21 | except InvalidObjectException: 22 | return None 23 | 24 | def from_guid(guid, options={}): 25 | "Generates ADObject based on GUID" 26 | try: 27 | q = ADObject.from_guid(guid, options) 28 | q.adjust_pyad_type() 29 | return q 30 | except InvalidObjectException: 31 | return None 32 | -------------------------------------------------------------------------------- /pyad/pyadconstants.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .pyadexceptions import * 3 | 4 | # http://msdn.microsoft.com/en-us/library/aa772263(VS.85).aspx 5 | ADS_GROUP_TYPE = { 6 | 'GLOBAL':0x2, 7 | 'LOCAL':0x4, 8 | 'UNIVERSAL':0x8, 9 | 'SECURITY_ENABLED':-0x80000000} 10 | 11 | # http://msdn.microsoft.com/en-us/library/aa772300.aspx 12 | ADS_USER_FLAG = { 13 | 'SCRIPT':0x1, 14 | 'ACCOUNTDISABLE':0x2, 15 | 'HOMEDIR_REQUIRED':0x8, 16 | 'LOCKOUT':0x10, 17 | 'PASSWD_NOTREQD':0x20, 18 | 'PASSWD_CANT_CHANGE':0x40, 19 | 'ENCRYPTED_TEXT_PASSWORD_ALLOWED':0x80, 20 | 'TEMP_DUPLICATE_ACCOUNT':0x100, 21 | 'NORMAL_ACCOUNT':0x200, 22 | 'INTERDOMAIN_TRUST_ACCOUNT':0x800, 23 | 'WORKSTATION_TRUST_ACCOUNT':0x1000, 24 | 'SERVER_TRUST_ACCOUNT':0x2000, 25 | 'DONT_EXPIRE_PASSWD':0x10000, 26 | 'MNS_LOGON_ACCOUNT':0x20000, 27 | 'SMARTCARD_REQUIRED':0x40000, 28 | 'TRUSTED_FOR_DELEGATION':0x80000, 29 | 'NOT_DELEGATED':0x100000, 30 | 'USE_DES_KEY_ONLY':0x200000, 31 | 'DONT_REQUIRE_PREAUTH':0x400000, 32 | 'PASSWORD_EXPIRED':0x800000, 33 | 'TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION':0x1000000} 34 | 35 | #http://msdn.microsoft.com/en-us/library/windows/desktop/aa772247(v=vs.85).aspx 36 | ADS_AUTHENTICATION_TYPE = { 37 | 'ADS_SECURE_AUTHENTICATION':0x1, 38 | 'ADS_USE_ENCRYPTION':0x2, 39 | 'ADS_USE_SSL':0x2, 40 | 'ADS_READONLY_SERVER':0x4, 41 | 'ADS_PROMPT_CREDENTIALS':0x8, 42 | 'ADS_FAST_BIND':0x20, 43 | 'ADS_USE_SIGNING':0x40, 44 | 'ADS_USE_SEALING':0x80, 45 | 'ADS_USE_DELEGATION':0x100, 46 | 'ADS_NO_REFERRAL_CHASING':0x400, 47 | 'ADS_AUTH_RESERVED':0x80000000} 48 | 49 | PYAD_CATEGORY_TYPE_OVERRIDE_MAPPPINGS = { 50 | "Person":"user", 51 | "Organizational-Unit":"organizationalUnit", 52 | "Domain-DNS":"domain"} 53 | 54 | WIN32_ERRORS = { 55 | 0x80072030:InvalidObjectException, 56 | 0x80072020:InvalidObjectException 57 | } 58 | 59 | # http://msdn.microsoft.com/en-us/library/aa705940(VS.85).aspx 60 | GENERIC_ADSI_ERRORS = { 61 | 0x00005011:('S_ADS_ERRORSOCCURRED', 62 | 'During a query, one or more errors occurred.', 63 | 'Verify that the search preference can be legally set and, if so, that it is properly set.', 64 | win32Exception), 65 | 0x00005012:('S_ADS_NOMORE_ROWS', 66 | 'The search operation has reached the last row.', 67 | 'Move on to the rest of the program.', 68 | win32Exception), 69 | 0x00005013:('S_ADS_NOMORE_COLUMNS', 70 | 'The search operation has reached the last column for the current row.', 71 | 'Move on to next row.', 72 | win32Exception), 73 | 0x80005000:('E_ADS_BAD_PATHNAME','An invalid ADSI pathname was passed.', 74 | 'Verify that the object exists on the directory server and check for typographic errors of the path.', 75 | win32Exception), 76 | 0x80005001:('E_ADS_INVALID_DOMAIN_OBJECT', 77 | 'An unknown ADSI domain object was requested', 78 | 'Verify the path of the domain object.', 79 | win32Exception), 80 | 0x80005002:('E_ADS_INVALID_USER_OBJECT', 81 | 'An unknown ADSI user object was requested.', 82 | 'Verify the existence of the user object, check for typos of the path and the user access right', 83 | win32Exception), 84 | 0x80005003:('E_ADS_INVALID_COMPUTER_OBJECT', 85 | 'An unknown ADSI computer object was requested.', 86 | 'Verify the existence of the computer object, check for typos of the path and the computer access rights.', 87 | win32Exception), 88 | 0x80005004:('E_ADS_UNKNOWN_OBJECT', 89 | 'An unknown ADSI object was requested.', 90 | 'Verify the name of and the access rights to the object.', 91 | win32Exception), 92 | 0x80005005:('E_ADS_PROPERTY_NOT_SET', 93 | 'The specified ADSI property was not set.', 94 | '', 95 | win32Exception), 96 | 0x80005006:('E_ADS_PROPERTY_NOT_SUPPORTED', 97 | 'The specified ADSI property is not supported.', 98 | 'Verify that the correct property is set.', 99 | win32Exception), 100 | 0x80005007:('E_ADS_PROPERTY_INVALID', 101 | 'The specified ADSI property is invalid.', 102 | 'Verify that the search preference can be legally set and, if so, that it is properly set.', 103 | win32Exception), 104 | 0x80005008:('E_ADS_BAD_PARAMETER', 105 | 'One or more input parameters are invalid.', 106 | 'Verify that the search preference can be legally set and, if so, that it is properly set.', 107 | win32Exception), 108 | 0x80005009:('E_ADS_OBJECT_UNBOUND', 109 | 'The specified ADSI object{ is not bound to a remote resource.', 110 | 'Call GetInfo on a newly created object after SetInfo has been called.', 111 | win32Exception), 112 | 0x8000500A:('E_ADS_PROPERTY_NOT_MODIFIED', 113 | 'The specified ADSI object has not been modified', 114 | '', 115 | win32Exception), 116 | 0x8000500B:('E_ADS_PROPERTY_MODIFIED', 117 | 'The specified ADSI object has been modified.', 118 | '', 119 | win32Exception), 120 | 0x8000500C:('E_ADS_CANT_CONVERT_DATATYPE', 121 | 'The data type cannot be converted to/from a native DS data type.', 122 | 'Verify that the correct data type is used and/or that there is sufficient schema data available to perform data type conversion.', 123 | win32Exception), 124 | 0x8000500D:('E_ADS_PROPERTY_NOT_FOUND', 125 | 'The property cannot be found in the cache.', 126 | 'Verify that attribute exists for particular object.', 127 | win32Exception), 128 | 0x8000500E:('E_ADS_OBJECT_EXISTS', 129 | 'The ADSI object already exists.', 130 | 'Use a different name to create the object.', 131 | win32Exception), 132 | 0x8000500F:('E_ADS_SCHEMA_VIOLATION', 133 | 'The attempted action violates the directory service schema rules.', 134 | '', 135 | win32Exception), 136 | 0x80005010:('E_ADS_COLUMN_NOT_SET', 137 | 'The specified column in the ADSI was not set.', 138 | '', 139 | win32Exception), 140 | 0x80005014:('E_ADS_INVALID_FILTER', 141 | 'The specified search filter is invalid.', 142 | 'Use the correct format of the filter accepted by the directory server.', 143 | win32Exception) 144 | } 145 | 146 | # http://msdn.microsoft.com/en-us/library/aa705941(VS.85).aspx 147 | GENERIC_COM_ERRORS = { 148 | 0x80004004:('E_ABORT','Operation aborted.'), 149 | 0x80004005:('E_FAIL','Unspecified error.'), 150 | 0x80004002:('E_NOINTERFACE','Interface not supported.'), 151 | 0x80004001:('E_NOTIMPL','Not implemented.'), 152 | 0x80004003:('E_POINTER','Invalid pointer.'), 153 | 0x8000FFFF:('E_UNEXPECTED','Catastrophic failure.')} 154 | -------------------------------------------------------------------------------- /pyad/pyadexceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from builtins import str 3 | class comException(Exception): 4 | def __init__(self, error_info, additional_info={}): 5 | self.error_info = error_info 6 | self.additional_info = additional_info 7 | 8 | def __str__(self): 9 | print("Error Constant: %s" % self.error_info['error_constant']) 10 | print("Error Code: %s" % str(self.error_info['error_code'])) 11 | #print "Error Message: %s" % self.error_info['error_message'] 12 | #print "type is ", self.error_info['error_message'].__class__ 13 | #return "%s (%s): %s" % (str(self.error_info['error_constant']), str(self.error_info['error_code']), str(self.error_info['error_message'])) 14 | 15 | class genericADSIException(comException): 16 | def __init__(self, error_info, additional_info={}): 17 | comException.__init__(error_info, additional_info) 18 | 19 | def __str__(self): 20 | return "%s (%s): %s" % (self.error_info['error_constant'], self.error_info['error_code'], self.error_info['error_message']) 21 | 22 | 23 | class win32Exception(comException): 24 | def __init__(self, error_info, additional_info={}): 25 | comException.__init__(self, error_info, additional_info) 26 | 27 | def __str__(self): 28 | return "%s: %s" % (self.error_info['error_code'], self.error_info['message']) 29 | 30 | 31 | class invalidOwnerException(Exception): 32 | def __str__(self): 33 | return "The submitted object is not eligible to own another object." 34 | 35 | 36 | class noObjectFoundException(Exception): 37 | def __str__(self): 38 | return "The requested object does not exist." 39 | 40 | 41 | class InvalidObjectException(noObjectFoundException, win32Exception): 42 | def __init__(self, error_info, additional_info): 43 | win32Exception.__init__(self, error_info, additional_info) 44 | 45 | 46 | class InvalidAttribute(AttributeError): 47 | def __init__(self, obj, attribute): 48 | self.obj, self.attribute = obj, attribute 49 | 50 | def __str__(self): 51 | return 'The attribute "%s" is not permitted by the schema definition of the object "%s" (the requested attribute does not exist).' % (self.attribute, self.obj) 52 | 53 | 54 | class noExecutedQuery(Exception): 55 | def __str__(self): 56 | return 'No query has been executed. Therefore there are no results to return. Execute a query before requesting results.' 57 | 58 | 59 | class invalidResults(Exception): 60 | def __init__(self, numberResults): 61 | self.__numberResults = numberResults 62 | 63 | def __str__(self): 64 | return 'The specified query returned %i results. getSingleResults only functions with a single result.' % self.__numberResults 65 | -------------------------------------------------------------------------------- /pyad/pyadutils.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import absolute_import 3 | from builtins import hex 4 | from builtins import chr 5 | from builtins import str 6 | from .adbase import * 7 | from datetime import timedelta 8 | 9 | def validate_credentials(username, password, domain=None): 10 | """Validates credentaials; returns a PyHANDLE object with a bool value 11 | of True if the credentials are valid, else returns None. 12 | Note that if the user would not be able to log on; for example, 13 | due to the account being expired; None will be returned. 14 | """ 15 | try: 16 | valid = win32security.LogonUser(username, domain, password, 17 | win32security.LOGON32_LOGON_NETWORK, 18 | win32security.LOGON32_PROVIDER_DEFAULT) 19 | assert valid, "Valid should ALWAYS have a true value" 20 | return valid 21 | except pywintypes.error: 22 | return None 23 | 24 | def convert_error_code(error_code): 25 | """Convert error code from the format returned by pywin32 to the format that Microsoft documents everything in.""" 26 | return error_code % 2 ** 32 27 | 28 | #expects the actualy pywintypes.com_error exception that's thrown... 29 | def interpret_com_exception(excp, additional_info={}): 30 | d = {} 31 | d['error_num'] = convert_error_code(excp.args[2][5]) 32 | # for some reason hex() includes the L for long in the hex... 33 | # however since it's a string, we don't care... 34 | # since L would never be in a hex code, we can safely just remove it. 35 | d['error_code'] = hex(d['error_num']).rstrip('L') 36 | if d['error_code'][0:7] == '0x80005': 37 | if d['error_num'] in list(GENERIC_ADSI_ERRORS.keys()): 38 | d['exception_type'] = 'known_generic_adsi_error' 39 | d['error_constant'] = GENERIC_ADSI_ERRORS[d['error_num']][0] 40 | d['message'] = ' '.join(GENERIC_ADSI_ERRORS[d['error_num']][1:3]) 41 | else: 42 | # this supposedly should not happen, but I'd rather be ready for 43 | # the case that Microsoft made a typo somewhere than die weirdly. 44 | d['error_constant'] = None 45 | d['exception_type'] = 'unknown_generic_adsi_error' 46 | d['message'] = 'unknown generic ADSI error' 47 | d['exception'] = genericADSIException 48 | elif d['error_code'][0:6] == '0x8007': 49 | d['exception_type'] = 'win32_error' 50 | d['error_constant'] = None 51 | # returns information about error from winerror.h file... 52 | d['message'] = win32api.FormatMessage(d['error_num']) 53 | elif d['error_num'] in list(GENERIC_COM_ERRORS.keys()): 54 | d['exception_type'] = 'generic_com_error' 55 | d['error_constant'] = GENERIC_COM_ERRORS[d['error_num']][0] 56 | d['message'] = GENERIC_COM_ERRORS[d['error_num']][1] 57 | else: 58 | d['exception_type'] = 'unknown' 59 | d['error_constant'] = None 60 | d['message'] = excp.args[2][4] 61 | d['additional_info'] = additional_info={} 62 | return d 63 | 64 | def pass_up_com_exception(excp, additional_info={}): 65 | if excp.__class__ in (genericADSIException, comException, win32Exception): 66 | raise excp 67 | else: 68 | info = interpret_com_exception(excp) 69 | type_ = info['exception_type'] 70 | if type_ == 'win32_error': 71 | # raise exception defined in WIN32_ERRORs if there is one... 72 | # otherwise, just raise a generic win32Exception 73 | raise WIN32_ERRORS.get(info['error_num'], win32Exception)(error_info=info, additional_info=additional_info) 74 | elif type_ == 'known_generic_adsi_error': 75 | raise GENERIC_ADSI_ERRORS[info['error_num']][3](error_info=info, additional_info=additional_info) 76 | elif type_ == 'unknown_generic_adsi_error': 77 | raise genericADSIException(error_info=info, additional_info=additional_info) 78 | else: 79 | raise comException(error_info=info, additional_info=additional_info) 80 | 81 | def convert_datetime(adsi_time_com_obj): 82 | """Converts 64-bit integer COM object representing time into a python datetime object.""" 83 | # credit goes to John Nielsen who documented this at 84 | # http://docs.activestate.com/activepython/2.6/pywin32/html/com/help/active_directory.html. 85 | 86 | high_part = int(adsi_time_com_obj.highpart) << 32 87 | low_part = int(adsi_time_com_obj.lowpart) 88 | date_value = ((high_part + low_part) - 116444736000000000) // 10000000 89 | # 90 | # The "fromtimestamp" function in datetime cannot take a 91 | # negative value, so if the resulting date value is negative, 92 | # explicitly set it to 18000. This will result in the date 93 | # 1970-01-01 00:00:00 being returned from this function 94 | # 95 | if date_value < 0: 96 | date_value = 18000 97 | return datetime.datetime.fromtimestamp(date_value) 98 | 99 | def convert_bigint(obj): 100 | # based on http://www.selfadsi.org/ads-attributes/user-usnChanged.htm 101 | h, l = obj.HighPart, obj.LowPart 102 | if l < 0: 103 | h += 1 104 | return (h << 32) + l 105 | 106 | def convert_timespan(obj): 107 | """Converts COM object representing timespan to a python timespan object.""" 108 | as_seconds = abs(convert_bigint(obj))/10000000 #number of 100 nanoseconds in a second 109 | return timedelta(seconds=as_seconds) 110 | 111 | def convert_guid(guid_object): 112 | return pywintypes.IID(guid_object, True) 113 | 114 | def convert_sid(sid_object): 115 | return pywintypes.SID(bytes(sid_object)) 116 | 117 | def generate_list(input): 118 | if type(input) is list: 119 | return input 120 | elif type(input) in (set,tuple): 121 | return list(input) 122 | else: 123 | return [input,] 124 | 125 | def escape_path(path): 126 | escapes = ( 127 | ('\+','+'), 128 | ('\*','*'), 129 | ('\(','('), 130 | ('\)',')'), 131 | ('\/','/'), 132 | ('\\,',',,'), 133 | ('\\','\\5c'), 134 | ('*','\\2a'), 135 | ('(','\\28'), 136 | (')','\\29'), 137 | ('/','\\2f'), 138 | ('+','\\2b'), 139 | (chr(0),'\\00') 140 | ) 141 | for char, escape in escapes: 142 | path = path.replace(char, escape) 143 | path = path.replace(",,","\\2c") 144 | return path 145 | 146 | def generate_ads_path(distinguished_name, type_, server=None, port=None): 147 | """Generates a proper ADsPath to be used when connecting to an active directory object or when searching active directory. 148 | 149 | Keyword arguments: 150 | - distinguished_name: DN of object or search base such as 'cn=zakir,ou=users,dc=mycompany,dc=com' (required). 151 | - type: 'GC' (global-catalog) or 'LDAP' to determine what directory to be searched (required). 152 | - server: FQDN of domain controller if necessary to connect to a particular server (optional unless port is defined). 153 | - port: port number for directory service if not default port. If port is specified, server must be specified (optional).""" 154 | 155 | if type_ == "LDAP" or type_ == "LDAPS": 156 | server = server if server else ADBase.default_ldap_server 157 | port = port if port else ADBase.default_ldap_port 158 | elif type_ == "GC": 159 | server = server if server else ADBase.default_gc_server 160 | port = port if port else ADBase.default_gc_port 161 | else: 162 | raise Exception("Invalid type specified.") 163 | 164 | ads_path = ''.join((type_,'://')) 165 | if server: 166 | ads_path = ''.join((ads_path,server)) 167 | if port: 168 | ads_path = ':'.join((ads_path,str(port))) 169 | ads_path = ''.join((ads_path,'/')) 170 | ads_path = ''.join((ads_path,escape_path(distinguished_name))) 171 | return ads_path 172 | -------------------------------------------------------------------------------- /pyad/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from tests_adbase import * 2 | from tests_adquery import * -------------------------------------------------------------------------------- /pyad/tests/pyadunittest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pyad 3 | 4 | class ADTestCase(unittest.TestCase): 5 | SANDBOX_OU = "ou=pyad,ou=services,ou=ris,ou=vpr,dc=iowa,dc=uiowa,dc=edu" 6 | SANDBOX_DOMAIN = 'DC=iowa,DC=uiowa,DC=edu' 7 | SANDBOX_FOREST = 'DC=uiowa,DC=edu' 8 | 9 | KNOWN_EXISTS_USER = 'durumericz' 10 | KNOWN_EXISTS_COMPUTER = 'VPR0751' 11 | KNOWN_DNE_OBJECT = "durumeric_z" 12 | 13 | def assertHasAttribute(self, obj, attribute): 14 | self.assertTrue(hasattr(obj._ldap_adsi_obj, attribute)) 15 | 16 | def assertAttributeValue(self, obj, attribute, value): 17 | self.assertEqual(obj._ldap_adsi_obj.GetEx(attribute), value) -------------------------------------------------------------------------------- /pyad/tests/tests_adbase.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .pyadunittest import * 3 | 4 | class TestADBase(ADTestCase): 5 | def setUp(self): 6 | # set all defaults back to their default 7 | pyad.adbase.ADBase.default_ldap_server = None 8 | pyad.adbase.ADBase.default_gc_server = None 9 | pyad.adbase.ADBase.default_ldap_port = None 10 | pyad.adbase.ADBase.default_gc_port = None 11 | 12 | def test_detected_forest(self): 13 | self.assertEqual(pyad.adbase.ADBase.default_domain, self.SANDBOX_DOMAIN) 14 | 15 | def test_detected_domain(self): 16 | self.assertEqual(pyad.adbase.ADBase.default_forest, self.SANDBOX_FOREST) 17 | 18 | def test_set_defaults(self): 19 | pyad.adbase.set_defaults(ldap_server = 'iowadc1', ldap_port = 389) 20 | self.assertEqual(pyad.adbase.ADBase.default_ldap_server, 'iowadc1') 21 | self.assertEqual(pyad.adbase.ADBase.default_ldap_port, 389) 22 | -------------------------------------------------------------------------------- /pyad/tests/tests_adquery.py: -------------------------------------------------------------------------------- 1 | from pyadunittest import * 2 | 3 | class TestADQuery(ADTestCase): 4 | def setUp(self): 5 | self.ad_query = pyad.adquery.ADQuery() 6 | 7 | def test_dne_rowcount(self): 8 | query = "cn = '%s'" % self.KNOWN_DNE_OBJECT 9 | self.ad_query.execute_query(where_clause = query) 10 | self.assertEqual(self.ad_query.get_row_count(), 0) 11 | 12 | def test_dne_single_result(self): 13 | query = "cn = '%s'" % self.KNOWN_DNE_OBJECT 14 | self.ad_query.execute_query(where_clause = query) 15 | self.assertRaises(pyad.pyadexceptions.invalidResults, self.ad_query.get_single_result) 16 | 17 | def test_dne_all_results(self): 18 | query = "cn = '%s'" % self.KNOWN_DNE_OBJECT 19 | self.ad_query.execute_query(where_clause = query) 20 | self.assertEqual(self.ad_query.get_all_results(), []) 21 | 22 | def test_single_rowcount(self): 23 | query = "cn = '%s'" % self.KNOWN_EXISTS_USER 24 | self.ad_query.execute_query(where_clause = query) 25 | self.assertEqual(self.ad_query.get_row_count(), 1) 26 | 27 | def test_single_single_result(self): 28 | query = "cn = '%s'" % self.KNOWN_EXISTS_USER 29 | self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) 30 | self.assertEqual(self.ad_query.get_single_result()['cn'],self.KNOWN_EXISTS_USER) 31 | 32 | def test_single_all_results(self): 33 | query = "cn = '%s'" % self.KNOWN_EXISTS_USER 34 | self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) 35 | self.assertEqual(self.ad_query.get_all_results()[0]['cn'],self.KNOWN_EXISTS_USER) 36 | 37 | def test_multiple_rowcount(self): 38 | query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) 39 | self.ad_query.execute_query(where_clause = query) 40 | self.assertEqual(self.ad_query.get_row_count(), 2) 41 | 42 | def test_multiple_single_result(self): 43 | query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) 44 | self.ad_query.execute_query(where_clause = query) 45 | self.assertRaises(pyad.pyadexceptions.invalidResults, self.ad_query.get_single_result) 46 | 47 | def test_multiple_all_results(self): 48 | query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) 49 | self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) 50 | r = map(lambda x: x['cn'], self.ad_query.get_all_results()) 51 | k = [self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER] 52 | self.assertEqual(r.sort(),k.sort()) 53 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | from setuptools import setup 4 | 5 | def read(fname): 6 | if os.path.exists(fname): 7 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 8 | else: 9 | return '' 10 | 11 | setup( 12 | name = "pyad", 13 | version = "0.5.15", 14 | author = "Zakir Durumeric", 15 | author_email = "zakird@gmail.com", 16 | maintainer = "Zakir Durumeric", 17 | maintainer_email = "zakird@gmail.com", 18 | download_url = "https://github.com/zakird/pyad/", 19 | url = "https://zakird.github.io/pyad/", 20 | description = "An Object-Oriented Active Directory management framework built on ADSI", 21 | license = "Apache License, Version 2.0", 22 | keywords = "python microsoft windows active directory AD adsi", 23 | packages=[ 24 | 'pyad' 25 | ], 26 | long_description = read('README.rst'), 27 | classifiers=[ 28 | "Development Status :: 5 - Production/Stable", 29 | "License :: OSI Approved :: Apache Software License", 30 | "Intended Audience :: System Administrators", 31 | "Natural Language :: English", 32 | "Operating System :: Microsoft :: Windows", 33 | "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP" 34 | ], 35 | install_requires=[ 36 | 'setuptools', 37 | 'future', 38 | ] 39 | ) 40 | --------------------------------------------------------------------------------