pyad/000755 000765 000024 00000000000 11654560147 012307 5ustar00zakirstaff000000 000000 pyad/._.DS_Store000644 000765 000024 00000000122 11654560330 014174 0ustar00zakirstaff000000 000000 Mac OS X  2 R pyad/.DS_Store000644 000765 000024 00000014004 11654560330 013763 0ustar00zakirstaff000000 000000 Bud1 IlocblobpyadIlocblobF( pyad.egg-infoIlocblob( README.rstIlocblobR(setup.pyIlocblob(test_handler.pyIlocblobFtestsIlocblob  @ @ @ @ E DSDB ` @ @ @pyad/.git/000755 000765 000024 00000000000 11652315003 013133 5ustar00zakirstaff000000 000000 pyad/._pyad000755 000765 000024 00000000341 11654560147 013405 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/pyad/000755 000765 000024 00000000000 11654560147 013244 5ustar00zakirstaff000000 000000 pyad/pyad.egg-info/000755 000765 000024 00000000000 11652313510 014722 5ustar00zakirstaff000000 000000 pyad/._README.rst000644 000765 000024 00000003336 11652315001 014201 0ustar00zakirstaff000000 000000 Mac OS X  2r:ATTR%com.apple.metadata:kMDItemWhereFroms!com.macromates.caretbplist00_0"Brautigam, Keith A" _Re: pyad documentation_. Basics ------ Active Directory objects are represented by standard python objects. There are classes for all major types of Active Directory objects: ADComputer, ADContainer, ADDomain, ADGroup, ADUser, all of which inherit from ADObject. It is possible to connect to objects via CN, DN, and GUID. Example:: import pyad u = pyad.aduser.ADUser.from_cn("user1") c = pyad.adcomputer.ADComputer.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") g = pyad.adgroup.ADGroup.from_cn("group1") It is possible to read attribute values in two ways.:: print u.displayName print u.get_attribute("displayName") Attributes can be set by calling clear_attribute, update_attribute, update_attributes, append_to_attribute, and remove_from_attribute. Example:: u.update_attribute("displayName", "new value") There are other helper methods available for managing attributes. We provide further examples below for common actions for each object type. Group Examples -------------- 1. Finding group members:: for object in g.get_members(recursive=False): print object 2. Adding an object to a group:: g.add_members(u) or:: u.add_to_group(g) 3. Set group scope:: g.set_group_scope("UNIVERSAL") User Examples ------------- 1. Set password:: u.set_password("new_plaintext_password") 2. Force password change on login:: u.force_pwd_change_on_login() Container Examples ------------------ 1. Find all objects in an OU:: ou = pyad.adcontainer.ADContainer.from_dn("OU=Workstations,DC=company,DC=com") for obj in ou.get_children(): print obj 2. Recursively find all computers below a certain OU:: for c in ou.get_children(recursive=True, filter=[pyad.adcomputer.ADComputer]): print c Creating Objects ---------------- It is possible to create objects through pyad. Example:: ou = pyad.adcontainer.ADContainer.from_dn("OU=Workstations,DC=company,DC=com") c = pyad.adcomputer.ADComputer.create( name = 'myworkstation2', container_object = ou, enable = True, optional_attributes = dict( description = "newly created computer" ) ) Querying -------- It is also possible to make queries to find objects. Example:: q = pyad.adquery.ADQuery() q.execute_query( attributes = ['distinguishedname', 'description'], where_clause = "cn like 'ws%'", base_dn = "dc=company,dc=com" ) for r in q.get_results(): print r['distinguishedname'] pyad/._setup.py000644 000765 000024 00000000273 11654560072 014235 0ustar00zakirstaff000000 000000 Mac OS X  2ATTR##com.macromates.caret{ column = 41; line = 19; }pyad/setup.py000644 000765 000024 00000002104 11654560072 014013 0ustar00zakirstaff000000 000000 import os from setuptools import setup def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name = "pyad", version = "0.4.9", author = "Zakir Durumeric", author_email = "zakird@gmail.com", maintainer = "Zakir Durumeric", maintainer_email = "zakird@gmail.com", url = "https://github.com/zakird/pyad", description = "An Object-Oriented Active Directory management framework built on ADSI", license = "GNUv3", keywords = "python microsoft windows active directory AD adsi", packages=[ 'pyad' ], long_description = read('README.rst'), classifiers=[ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", "Intended Audience :: System Administrators", "Natural Language :: English", "Operating System :: Microsoft :: Windows", "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP" ], install_requires=[ 'setuptools', 'pywin32' ] ) pyad/._test_handler.py000644 000765 000024 00000000341 11615404422 015536 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/test_handler.py000644 000765 000024 00000000132 11615404422 015317 0ustar00zakirstaff000000 000000 from pyad import * from tests import * if __name__ == '__main__': unittest.main()pyad/._tests000755 000765 000024 00000000341 11654560147 013612 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/tests/000755 000765 000024 00000000000 11654560147 013451 5ustar00zakirstaff000000 000000 pyad/tests/.___init__.py000755 000765 000024 00000000341 11615406210 015763 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/tests/__init__.py000755 000765 000024 00000000067 11615406210 015553 0ustar00zakirstaff000000 000000 from tests_adbase import * from tests_adquery import *pyad/tests/._pyadunittest.py000755 000765 000024 00000000272 11615406750 016775 0ustar00zakirstaff000000 000000 Mac OS X  2ATTR""com.macromates.caret{ column = 36; line = 3; }pyad/tests/pyadunittest.py000755 000765 000024 00000001145 11615406750 016560 0ustar00zakirstaff000000 000000 import unittest import pyad class ADTestCase(unittest.TestCase): SANDBOX_OU = "ou=pyad,ou=services,ou=ris,ou=vpr,dc=iowa,dc=uiowa,dc=edu" SANDBOX_DOMAIN = 'DC=iowa,DC=uiowa,DC=edu' SANDBOX_FOREST = 'DC=uiowa,DC=edu' KNOWN_EXISTS_USER = 'durumericz' KNOWN_EXISTS_COMPUTER = 'VPR0751' KNOWN_DNE_OBJECT = "durumeric_z" def assertHasAttribute(self, obj, attribute): self.assertTrue(hasattr(obj._ldap_adsi_obj, attribute)) def assertAttributeValue(self, obj, attribute, value): self.assertEqual(obj._ldap_adsi_obj.GetEx(attribute), value)pyad/tests/._tests_adbase.py000755 000765 000024 00000000270 11615406732 016677 0ustar00zakirstaff000000 000000 Mac OS X  2ATTR  com.macromates.caretxR<[k0?'3/«pyad/tests/tests_adbase.py000755 000765 000024 00000001527 11615406732 016470 0ustar00zakirstaff000000 000000 from pyadunittest import * class TestADBase(ADTestCase): def setUp(self): # set all defaults back to their default pyad.adbase.ADBase.default_ldap_server = None pyad.adbase.ADBase.default_gc_server = None pyad.adbase.ADBase.default_ldap_port = None pyad.adbase.ADBase.default_gc_port = None def test_detected_forest(self): self.assertEqual(pyad.adbase.ADBase.default_domain, self.SANDBOX_DOMAIN) def test_detected_domain(self): self.assertEqual(pyad.adbase.ADBase.default_forest, self.SANDBOX_FOREST) def test_set_defaults(self): pyad.adbase.set_defaults(ldap_server = 'iowadc1', ldap_port = 389) self.assertEqual(pyad.adbase.ADBase.default_ldap_server, 'iowadc1') self.assertEqual(pyad.adbase.ADBase.default_ldap_port, 389)pyad/tests/._tests_adquery.py000755 000765 000024 00000000270 11615411474 017131 0ustar00zakirstaff000000 000000 Mac OS X  2ATTR  com.macromates.caretxR<[k0?'3/«pyad/tests/tests_adquery.py000755 000765 000024 00000005102 11615411474 016713 0ustar00zakirstaff000000 000000 from pyadunittest import * class TestADQuery(ADTestCase): def setUp(self): self.ad_query = pyad.adquery.ADQuery() def test_dne_rowcount(self): query = "cn = '%s'" % self.KNOWN_DNE_OBJECT self.ad_query.execute_query(where_clause = query) self.assertEqual(self.ad_query.get_row_count(), 0) def test_dne_single_result(self): query = "cn = '%s'" % self.KNOWN_DNE_OBJECT self.ad_query.execute_query(where_clause = query) self.assertRaises(pyad.pyadexceptions.invalidResults, self.ad_query.get_single_result) def test_dne_all_results(self): query = "cn = '%s'" % self.KNOWN_DNE_OBJECT self.ad_query.execute_query(where_clause = query) self.assertEqual(self.ad_query.get_all_results(), []) def test_single_rowcount(self): query = "cn = '%s'" % self.KNOWN_EXISTS_USER self.ad_query.execute_query(where_clause = query) self.assertEqual(self.ad_query.get_row_count(), 1) def test_single_single_result(self): query = "cn = '%s'" % self.KNOWN_EXISTS_USER self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) self.assertEqual(self.ad_query.get_single_result()['cn'],self.KNOWN_EXISTS_USER) def test_single_all_results(self): query = "cn = '%s'" % self.KNOWN_EXISTS_USER self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) self.assertEqual(self.ad_query.get_all_results()[0]['cn'],self.KNOWN_EXISTS_USER) def test_multiple_rowcount(self): query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) self.ad_query.execute_query(where_clause = query) self.assertEqual(self.ad_query.get_row_count(), 2) def test_multiple_single_result(self): query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) self.ad_query.execute_query(where_clause = query) self.assertRaises(pyad.pyadexceptions.invalidResults, self.ad_query.get_single_result) def test_multiple_all_results(self): query = "cn = '%s' or cn = '%s'" % (self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER) self.ad_query.execute_query(attributes=("cn","distinguishedname"),where_clause = query) r = map(lambda x: x['cn'], self.ad_query.get_all_results()) k = [self.KNOWN_EXISTS_USER, self.KNOWN_EXISTS_COMPUTER] self.assertEqual(r.sort(),k.sort()) pyad/pyad.egg-info/dependency_links.txt000644 000765 000024 00000000001 11652311542 020773 0ustar00zakirstaff000000 000000 pyad/pyad.egg-info/PKG-INFO000644 000765 000024 00000011275 11652311542 016030 0ustar00zakirstaff000000 000000 Metadata-Version: 1.0 Name: pyad Version: 0.4.9 Summary: A pure Python Object-Oriented Active Directory management framework built on ADSI through pywin32. Home-page: UNKNOWN Author: Zakir Durumeric Author-email: zakird@gmail.com License: GNUv3 Description: Introduction ------------ pyad is a python library designed to provide a simple, object oriented interface to Active Directory through ADSI on the Windows platform. pyad requires pywin32, available at http://sourceforge.net/projects/pywin32. pyad is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GNU General Public License is available at . Basics ------ Active Directory objects are represented by standard python objects. There are classes for all major types of Active Directory objects: ADComputer, ADContainer, ADDomain, ADGroup, ADUser, all of which inherit from ADObject. It is possible to connect to objects via CN, DN, and GUID. Example: import pyad u = pyad.aduser.ADUser.from_cn("user1") c = pyad.adcomputer.ADComputer.from_dn("cn=WS1,ou=Workstations,dc=domain,dc=com") g = pyad.adgroup.ADGroup.from_cn("group1") It is possible to read attribute values in two ways. print u.displayName print u.get_attribute("displayName") Attributes can be set by calling clear_attribute, update_attribute, update_attributes, append_to_attribute, and remove_from_attribute. Example: u.update_attribute("displayName", "new value") There are other helper methods available for managing attributes. We provide further examples below for common actions for each object type. Group Examples -------------- 1. Finding group members: for object in g.get_members(recursive=False): print object 2. Adding an object to a group: g.add_members(u) or u.add_to_group(g) 3. Set group scope: g.set_group_scope("UNIVERSAL") User Examples ------------- 1. Set password: u.set_password("new_plaintext_password") 2. Force password change on login: u.force_pwd_change_on_login() Container Examples ------------------ 1. Find all objects in an OU: ou = pyad.adcontainer.ADContainer.from_dn("OU=Workstations,DC=company,DC=com") for obj in ou.get_children(): print obj 2. Recursively find all computers below a certain OU: for c in ou.get_children(recursive=True, filter=[pyad.adcomputer.ADComputer]): print c Creating Objects ---------------- It is possible to create objects through pyad. Example: ou = pyad.adcontainer.ADContainer.from_dn("OU=Workstations,DC=company,DC=com") c = pyad.adcomputer.ADComputer.create( name = 'myworkstation2', container_object = ou, enable = True, optional_attributes = dict( description = "newly created computer" ) ) Querying -------- It is also possible to make queries to find objects. Example: q = pyad.adquery.ADQuery() q.execute_query( attributes = ['distinguishedname', 'description'], where_clause = "cn like 'ws%'", base_dn = "dc=company,dc=com" ) for r in q.get_results(): print r['distinguishedname'] Keywords: python microsoft windows active directory AD adsi Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: BSD License Classifier: Intended Audience :: System Administrators Classifier: Natural Language :: English Classifier: Operating System :: Microsoft :: Windows Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP pyad/pyad.egg-info/requires.txt000644 000765 000024 00000000022 11652311542 017317 0ustar00zakirstaff000000 000000 setuptools pywin32pyad/pyad.egg-info/SOURCES.txt000644 000765 000024 00000000617 11652311542 016615 0ustar00zakirstaff000000 000000 README setup.py pyad/__init__.py pyad/adbase.py pyad/adcomputer.py pyad/adcontainer.py pyad/addomain.py pyad/adgroup.py pyad/adobject.py pyad/adquery.py pyad/adsearch.py pyad/aduser.py pyad/pyad.py pyad/pyadconstants.py pyad/pyadexceptions.py pyad/pyadutils.py pyad.egg-info/PKG-INFO pyad.egg-info/SOURCES.txt pyad.egg-info/dependency_links.txt pyad.egg-info/requires.txt pyad.egg-info/top_level.txtpyad/pyad.egg-info/top_level.txt000644 000765 000024 00000000005 11652311542 017452 0ustar00zakirstaff000000 000000 pyad pyad/pyad/.___init__.py000644 000765 000024 00000000441 11615374246 015571 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/__init__.py000644 000765 000024 00000000163 11615374246 015355 0ustar00zakirstaff000000 000000 __all__ = ["adquery", "adsearch", "adobject", "adcomputer", "adcontainer", "addomain", "adgroup", "aduser", "pyad"]pyad/pyad/._adbase.py000644 000765 000024 00000000356 11620343755 015253 0ustar00zakirstaff000000 000000 Mac OS X  2ATTR2com.apple.TextEncoding#com.macromates.caretutf-8;134217984{ column = 29; line = 35; }pyad/pyad/adbase.py000644 000765 000024 00000003314 11620343755 015033 0ustar00zakirstaff000000 000000 import sys import datetime import time import types import xml.dom.minidom as xml # Since we're depending on ADSI, you have to be on windows... if sys.platform != 'win32': raise Exception("Must be running Windows in order to use pyad.") try: import win32api import pywintypes import win32com.client except ImportError: raise Exception("pywin32 library required. Download from http://sourceforge.net/projects/pywin32/") # Import constants and other common elements. from pyadconstants import * from pyadexceptions import * # create connection to ADSI COM object _adsi_provider = win32com.client.Dispatch('ADsNameSpaces') # Discover default domain and forest information __default_domain_obj = _adsi_provider.GetObject('',"LDAP://rootDSE") # connecting to rootDSE will connect to the domain that the # current logged-in user belongs to.. which is generally the # domain under question and therefore becomes the default domain. _default_detected_forest = __default_domain_obj.Get("rootDomainNamingContext") _default_detected_domain = __default_domain_obj.Get("defaultNamingContext") class ADBase(object): """Base class that is utilized by all objects within package to help store defaults. (search, query, all AD objects)""" default_ldap_server = None default_gc_server = None default_ldap_port = None default_gc_port = None default_domain = _default_detected_domain default_forest = _default_detected_forest adsi_provider = _adsi_provider def set_defaults(**kwargs): for k, v in kwargs.iteritems(): assert k in ('ldap_server','gc_server','ldap_port','gc_port') setattr(ADBase, '_'.join(('default',k)), v)pyad/pyad/._adcomputer.py000644 000765 000024 00000000443 11651213404 016163 0ustar00zakirstaff000000 000000 Mac OS X  2#ATTR#kIcom.apple.quarantine"com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mail{ column = 50; line = 7; }pyad/pyad/adcomputer.py000644 000765 000024 00000001500 11651213404 015741 0ustar00zakirstaff000000 000000 from adobject import * class ADComputer(ADObject): """""" @classmethod def create(cls, name, container_object, enable=True, optional_attributes={}): assert type(name) == str assert container_object.__class__.__name__ == 'ADContainer' return container_object.create_computer(name=name,enable=enable,optional_attributes=optional_attributes) def get_creator(self): """returns ADUser object of the user who added the computer to the domain. Returns None if user no longer exists.""" try: sid = str(pyadutils.convert_sid(self.get_attribute('mS-DS-CreatorSID', False))).split(':')[1] dn = adsearch.by_sid(sid) return ADUser(dn) except: return None ADObject._py_ad_object_mappings['computer'] = ADComputerpyad/pyad/._adcontainer.py000644 000765 000024 00000000443 11615374246 016323 0ustar00zakirstaff000000 000000 Mac OS X  2#ATTR#kIcom.apple.quarantine"com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mail{ column = 32; line = 9; }pyad/pyad/adcontainer.py000644 000765 000024 00000006711 11615374246 016112 0ustar00zakirstaff000000 000000 from adobject import * from aduser import ADUser from adcomputer import ADComputer from adgroup import ADGroup class ADContainer(ADObject): def get_children_iter(self, recursive=False, filter_=None): for com_object in self._ldap_adsi_obj: q = ADObject.from_com_object(com_object) q.adjust_pyad_type() if q.type == 'organizationalUnit' and recursive: for c in q.get_children_iter(recursive=recursive): if not filter_ or c.__class__ in filter_: yield c if not filter or q.__class__ in filter_: yield q def get_children(self, recursive=False, filter_=None): return list(self.get_children_iter(recursive=recursive, filter_=filter_)) def __create_object(self, type_, name): prefix = 'ou' if type_ == 'organizationalUnit' else 'cn' prefixed_name = '='.join((prefix,name)) return self._ldap_adsi_obj.Create(type_, prefixed_name) def create_user(self, name, password=None, upn_suffix=None, enable=True,optional_attributes={}): try: if not upn_suffix: upn_suffix = self.get_domain().get_default_upn() upn = '@'.join((name, upn_suffix)) obj = self.__create_object('user', name) obj.Put('sAMAccountName', name) obj.Put('userPrincipalName', upn) obj.SetInfo() pyadobj = ADUser.from_com_object(obj) if enable: pyadobj.enable() if password: pyadobj.set_password(password) pyadobj.update_attributes(optional_attributes) return pyadobj except pywintypes.com_error, e: pyadutils.pass_up_com_exception(e) def create_group(self, name, security_enabled=True, scope='GLOBAL', optional_attributes={}): try: obj = self.__create_object('group', name) obj.Put('sAMAccountName',name) val = pyADConstants.ADS_GROUP_TYPE[scope] if security_enabled: val = val | pyADConstants.ADS_GROUP_TYPE['SECURITY_ENABLED'] obj.Put('groupType',val) obj.SetInfo() pyadobj = ADGroup.from_com_object(obj) pyadobj.update_attributes(optional_attributes) return pyadobj except pywintypes.com_error, e: pyadutils.pass_up_com_exception(e) def create_container(self, name, optional_attriutes={}): try: obj = self.__create_object('organizationalUnit', name) obj.SetInfo() pyadobj = ADContainer.from_com_object(obj) pyadobj.update_attributes(optional_attributes) return pyadobj except pywintypes.com_error, e: pyadutils.pass_up_com_exception(e) def create_computer(self, name, enable=True,optional_attributes={}): try: obj = self.__create_object('computer', name) obj.Put('sAMAccountName', name) obj.SetInfo() pyadobj = ADComputer.from_com_object(obj) if enable: pyadobj.enable() pyadobj.update_attributes(optional_attributes) return pyadobj except pywintypes.com_error, e: pyadutils.pass_up_com_exception(e) def remove_child(self, child): self._ldap_adsi_obj.Delete(child.type, child.prefixed_cn) ADObject._py_ad_object_mappings['organizationalUnit'] = ADContainerpyad/pyad/._addomain.py000644 000765 000024 00000000441 11615374246 015606 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/addomain.py000644 000765 000024 00000001304 11615374246 015370 0ustar00zakirstaff000000 000000 from adcontainer import * class ADDomain(ADContainer): def get_default_upn(self): """Returns the default userPrincipalName for the domain.""" self._ldap_adsi_obj.GetInfoEx(["canonicalName",],0) return self._ldap_adsi_obj.get("canonicalName").rstrip('/') def __get_domain(self): if self._domain_pyad_obj is None: domain_path = 'dc=' + self.dn.lower().split("dc=",1)[1] self._domain_pyad_obj = ADDomain.from_dn(domain_path, options={'server':self.default_ldap_server,'port':self.default_ldap_port}) return self._domain_pyad_obj ADObject.get_domain = __get_domain ADObject._py_ad_object_mappings['domain'] = ADDomainpyad/pyad/._adgroup.py000644 000765 000024 00000000441 11615374246 015473 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/adgroup.py000644 000765 000024 00000016027 11615374246 015265 0ustar00zakirstaff000000 000000 from adobject import * class ADGroup(ADObject): @classmethod def create(cls, name, container_object, security_enabled=True, scope='GLOBAL', optional_attributes={}): return container_object.create_group(name=name, security_enabled=security_enabled, scope=scope, optional_attributes=optional_attributes) def add_members(self, members): """Accepts a list of pyAD objects or a single pyAD object and adds as members to the group.""" return self.append_to_attribute('member', map(lambda member: member.dn, pyadutils.generate_list(members))) def remove_members(self, members): """Accepts a list of pyAD objects or a single pyAD object and removes these as members from the group.""" return self.remove_from_attribute('member', map(lambda member: member.dn, pyadutils.generate_list(members))) def remove_all_members(self): """Removes all members of the group.""" return self.remove_from_attribute('member',self.get_attribute('member')) def get_members(self, recursive=False, ignoreGroups=False): """Returns a list of group members. recursive - True/False. Determines whether to recursively traverse through nested groups. ignoreGroups - True/False. Determines whether or not to return an ADGroup objects in list or to ignore them.""" return self._get_members(recursive, ignoreGroups, []) def _get_members(self, recursive, ignoreGroups, processedGroups): """Returns a list of pyAD objects that are members of the group.""" processedGroups.append(self.guid) # we need to keep track of which groups have been enumerated so far so that # we don't enter an infinite loop accidentally if group A is a member # of group B and group B is a member of group A. m = [] for dn in self.get_attribute('member'): pyADobj = pyAD(dn) if pyADobj.type == 'group' and pyADobj.guid not in processedGroups: if recursive: m.extend(pyADobj._get_members(recursive=True, ignoreGroups=ignoreGroups, processedGroups=processedGroups)) if not ignoreGroups: m.append(pyADobj) elif pyADobj.type != "group": m.append(pyADobj) return list((set(m))) # converting to set removes duplicates def sync_membership(self, new_population): "Synchronizes membership of group so that it matches the list of entries in new_population" current_members = set(self.get_members()) new_population = set(new_population) self.add_members(list(new_population - current_members)) self.remove_members(list(current_members - new_population)) def check_contains_member(self, check_member, recursive=False): """Checks whether a pyAD object is a member of the group. check_member expects a pyAD object to be checked. recursive expects True/False which determines whether the group membership will be searched recursively.""" if check_member in self.get_members(recursive=recursive, ignoreGroups=False): return True else: return False def get_group_scope(self): """Returns the group scope GLOBAL, UNIVERSAL, or LOCAL.""" group_type = self.get_attribute('groupType', False) if group_type & pyADConstants.ADS_GROUP_TYPE['GLOBAL'] == pyADConstants.ADS_GROUP_TYPE['GLOBAL']: return 'GLOBAL' elif group_type & pyADConstants.ADS_GROUP_TYPE['UNIVERSAL'] == pyADConstants.ADS_GROUP_TYPE['UNIVERSAL']: return 'UNIVERSAL' else: return 'LOCAL' def set_group_scope(self, new_scope): """Sets group scope. new_scope expects GLOBAL, UNIVERSAL, or LOCAL.""" if new_scope in ('LOCAL','GLOBAL','UNIVERSAL'): self.update_attribute('groupType',(self.get_attribute('groupType',False) & pyADConstants.ADS_GROUP_TYPE['SECURITY_ENABLED']) | pyADConstants.ADS_GROUP_TYPE[new_scope]) else: raise InvalidValue("new_scope",new_scope,('LOCAL','GLOBAL','UNIVERSAL')) def get_group_type(self): """Returns group type DISTRIBUTION or SECURITY.""" if self.get_attribute('groupType',False) in (2,4,8): # 0x2, 0x4, 0x8 are the distribution group types since a security group must include -0x80000000. return 'DISTRIBUTION' else: return 'SECURITY' def set_group_type(self, new_type): """Sets group type. new_type expects DISTRIBUTION or SECURITY.""" if new_type == 'DISTRIBUTION': self.update_attribute('groupType',(self.get_attribute('groupType',False) ^ pyADConstants.ADS_GROUP_TYPE['SECURITY_ENABLED'])) elif new_type == 'SECURITY': self.update_attribute('groupType',(self.get_attribute('groupType',False)\ ^ pyADConstants.ADS_GROUP_TYPE['SECURITY_ENABLED'])\ | _ADS_GROUP_TYPE['SECURITY_ENABLED']) else: raise InvalidValue("new_type",new_type,('DISTRIBUTION','SECURITY')) ADObject._py_ad_object_mappings['group'] = ADGroup def __get_memberOfs(self, recursive=False, scope='all'): return self._get_memberOfs(recursive, scope, []) def __is_member_of(self, group, recursive=False): return group in self.get_memberOfs(recursive=recursive) def ___p_get_memberOfs(self, recursive=False, scope='all', processed_groups=[]): """Returns a list of groups (ADGroup objects) that the current object is a member of. recursive - True/False. This determines whether to return groups that the object is nested into indirectly. scope - domain, forest, all. This determines whether to only return group membership within the current domain (queries from domain) (scope=domain), the forest (will only include universal groups, queries from global catalog) (scope=forest), or both (scope=all) processed_groups - reserved, leave empty.""" if self not in processed_groups: if scope in ('domain','all'): for dn in self.get_attribute('memberOf'): obj = ADGroup.from_dn(dn) if recursive and obj not in processed_groups and dn != self.dn: processed_groups.extend(obj._get_memberOfs(recursive, scope, processed_groups)) processed_groups.append(obj) if scope in ('forest','all'): for dn in self.get_attribute('memberOf',source='GC'): obj = ADGroup.from_dn(dn) if recursive and obj not in processed_groups and dn != self.dn: processed_groups.extend(obj._get_memberOfs(recursive, scope, processed_groups)) processed_groups.append(obj) return list(set(processed_groups)) ADObject.get_memberOfs = __get_memberOfs ADObject.is_member_of = __is_member_of ADObject._get_memberOfs = ___p_get_memberOfs pyad/pyad/._adobject.py000644 000765 000024 00000000441 11615400164 015572 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/adobject.py000644 000765 000024 00000047242 11615400164 015367 0ustar00zakirstaff000000 000000 import adsearch import pyadutils from adbase import * class ADObject(ADBase): """Python object that represents any active directory object.""" _ldap_adsi_obj = None _gc_adsi_obj = None _schema_adsi_obj = None _domain_pyad_obj = None _mandatory_attributes = None _optional_attributes = None _py_ad_object_mappings = {} def __init__(self, distinguished_name=None, adsi_ldap_com_object=None, options={}): if adsi_ldap_com_object: self._ldap_adsi_obj = adsi_ldap_com_object elif distinguished_name: if 'server' in options: self.default_ldap_server = options['server'] if 'port' in options: self.default_ldap_port = options['port'] self.__ads_path = pyadutils.generate_ads_path(distinguished_name, 'LDAP', self.default_ldap_server, self.default_ldap_port) try: self._ldap_adsi_obj = self.adsi_provider.getObject('',self.__ads_path) except pywintypes.com_error, excpt: additional_info = { 'distinguished_name':distinguished_name, 'server':self.default_ldap_server, 'port':self.default_ldap_port } pyadutils.pass_up_com_exception(excpt, additional_info) else: raise Exception("Either a distinguished name or a COM object must be provided to create an ADObject") # by pulling the DN from object instead of what is passed in, we gaurantee correct capitalization self.__distinguished_name = self.get_attribute('distinguishedName', False) self.__object_guid = pyadutils.convert_guid(self.get_attribute('objectGUID', False)) # Set pyAD Object Type occn = self.get_attribute('objectCategory',False) if occn: # pull out CN from DN object_category_cn = occn.split('=',1)[1].split(",",1)[0] # some object categories are not very human readable so we provide the option to override if object_category_cn in PYAD_CATEGORY_TYPE_OVERRIDE_MAPPPINGS: self._type = PYAD_CATEGORY_TYPE_OVERRIDE_MAPPPINGS[object_category_cn] else: self._type = object_category_cn.lower() else: self._type = 'unknown' @classmethod def from_guid(cls, guid, options={}): "Generates ADObject based on GUID" guid = "" % guid.strip('}').strip('{') return cls.from_dn(guid, options) @classmethod def from_dn(cls, distinguished_name, options={}): "Generates ADObject based on distinguished name" return cls(distinguished_name, None, options) @classmethod def from_cn(cls, cn, search_base=None, options={}): return cls(adsearch.by_cn(cn, search_base, options), None, options) @classmethod def from_com_object(cls, com_object): "Generates ADObject based on an existing ADSI com object" return cls(distinguished_name=None, adsi_ldap_com_object=com_object) def __get_prefixed_cn(self): prefix = 'ou' if self.type == 'organizationalUnit' else 'cn' return '='.join((prefix, self.get_attribute(prefix, False))) dn = property(fget=lambda self: self.__distinguished_name, doc="Distinguished Name (DN) of the object") prefixed_cn = property(fget=__get_prefixed_cn, doc="Prefixed CN (such as 'cn=mycomputer' or 'ou=mycontainer' of the object") guid = property(fget=lambda self: self.__object_guid, doc="Object GUID of the object") adsPath = property(fget=lambda self: self.__ads_path, doc="ADsPath of Active Directory object (such as 'LDAP://cn=me,...,dc=com'") type = property(fget=lambda self: self._type, doc="pyAD object type (user, computer, group, organizationalUnit, domain).") parent_container_path = property(fget=lambda self: self.dn.split(',',1)[1], doc="Returns the DN of the object's parent container.") guid_str = property(fget=lambda self: str(self.guid)[1:-1], doc="Object GUID of the object") def __hash__(self): # guid is always unique so that we can depend on that for providing a unique hash return hash(self.guid) def __str__(self): return "<%s '%s'>" % (self.__class__.__name__, self.dn) __repr__ = __str__ def __cmp__(self, other): # it doesn't make sense why you'd ever have to decide if one GUID was larger than the other, # but it's important to be able to know if two pyAD objects represent the same AD object. if (self.guid == other.guid): return 0 elif (self.guid < other.guid): return -1 else: return 1 def __getattr__(self, attribute): # allow people to call for random attributes on the ADObject # as long as they exist in Active Directory. if hasattr(self._ldap_adsi_obj, attribute): return self.get_attribute(attribute, False) else: raise AttributeError(attribute) def _flush(self): "Commits any changes to the AD object." return self._ldap_adsi_obj.SetInfo() def _init_global_catalog_object(self, options={}): """Initializes the global catalog ADSI com object to be used when querying the global catalog instead of the domain directly.""" if not self._gc_adsi_obj: self._gc_adsi_obj = _adsi_provider.GetObject('',pyadutils.generate_ads_path(self.dn, 'GC', options.get('server'), options.get('port'))) def _init_schema_object(self): if not self._schema_adsi_obj: self._schema_adsi_obj = win32com.client.GetObject(self._ldap_adsi_obj.schema) def get_mandatory_attributes(self): """Returns a list of mandatory attributes for the particular object. These attributes are guaranteed to be defined.""" self._init_schema_object() if not self._mandatory_attributes: self._mandatory_attributes = list(self._schema_adsi_obj.MandatoryProperties) return self._mandatory_attributes def get_optional_attributes(self): """Returns a list of optional attributes for the particular object. These attributes may be defined, but are not guaranteed to be.""" self._init_schema_object() if not self._optional_attributes: self._optional_attributes = list(self._schema_adsi_obj.OptionalProperties) return self._optional_attributes def get_allowed_attributes(self): """Returns a list of allowed attributes for the particular object. These attributes may be defined, but are not guaranteed to be.""" return list(set(self.get_mandatory_attributes() + self.get_optional_attributes())) def get_attribute(self, attribute, always_return_list=True, source='LDAP'): """Returns the value of any allowable LDAP attribute of the specified object. Keyword arguments: attribute -- any schema-allowed LDAP attribute (case insensitive). The attribute does not need to be defined. always_return_list -- if an attribute has a single value, this specifies whether to return only the value or to return a list containing the single value. Similarly, if true, a query on an undefined attribute will return an empty list instead of a None object. If querying an attribute known to only contain at most one element, then it is easier to set to false. Otherwise, if querying a potentially multi-valued attribute, it is safest to leave at default. source -- either 'LDAP' or 'GC' Note to experienced ADSI users: - If an attribute is undefined, getAttribute() will return None or [] and will not choke on the attribute. - In regards to always_return_list, True has similar behavior to getEx() whereas False is similar to Get().""" if not hasattr(self._ldap_adsi_obj, attribute): raise InvalidAttribute(self.dn, attribute) else: try: if source == 'LDAP': value = self._ldap_adsi_obj.GetEx(attribute) elif source == 'GC': self._init_global_catalog_object() value = self._gc_adsi_obj.GetEx(attribute) if len(value) == 1 and not always_return_list: return value[0] else: return list(value) # this just means that the attribute doesn't have a value which # we imply means null instead of throwing an error.. except pywintypes.com_error, excpt: if pyadutils.interpret_com_exception(excpt)['error_constant'] == 'E_ADS_PROPERTY_NOT_FOUND': return [] if always_return_list else None else: pyadutils.pass_up_com_exception(excpt, {'attribute':attribute}) def _set_attribute(self, attribute, action, new_value): if not hasattr(self._ldap_adsi_obj, attribute): raise InvalidAttribute(self.dn, attribute) else: try: self._ldap_adsi_obj.putEx(action, attribute, new_value) except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) def clear_attribute(self, attribute): """Clears (removes) the specified LDAP attribute from the object. Identical to setting the attribute to None or [].""" if self.get_attribute(attribute) != []: self._set_attribute(attribute, 1, []) self._flush() def update_attribute(self, attribute, newvalue, no_flush=False): """Updates any mutable LDAP attribute for the object. If you are adding or removing values from a multi-valued attribute, see append_to_attribute and remove_from_attribute.""" if newvalue in ((),[],None,''): return self.clear_attribute(attribute) elif pyadutils.generate_list(newvalue) != self.get_attribute(attribute): self._set_attribute(attribute, 2, pyadutils.generate_list(newvalue)) if not no_flush: self._flush() def update_attributes(self, attribute_value_dict): """Updates multiple attributes in a single transaction attribute_value_dict should contain a dictionary of values keyed by attribute name""" for k, v in attribute_value_dict.iteritems(): self.update_attribute(k,v,True) self._flush() def append_to_attribute(self, attribute, valuesToAppend): """Appends values in list valuesToAppend to the specified multi-valued attribute. valuesToAppend can contain a single value or a list of multiple values.""" difference = list(set(pyadutils.generate_list(valuesToAppend)) - set(self.get_attribute(attribute))) if len(difference) != 0: self._set_attribute(attribute,3,difference) self._flush() def remove_from_attribute(self, attribute, valuesToRemove): """Removes any values in list valuesToRemove from the specified multi-valued attribute.""" difference = list(set(pyadutils.generate_list(valuesToRemove)) & set(self.get_attribute(attribute))) if len(difference) != 0: self._set_attribute(attribute,4,difference) self._flush() def get_user_account_control_settings(self): """Returns a dictionary of settings stored within UserAccountControl. Expected keys for the dictionary are the same as keys in the ADS_USER_FLAG dictionary. Further information on these values can be found at http://msdn.microsoft.com/en-us/library/aa772300.aspx.""" d = {} auc = self.get_attribute('UserAccountControl',False) for key, value in ADS_USER_FLAG.iteritems(): d[key] = True if auc & value == value else False return d def set_user_account_control_setting(self, userFlag, newValue): """Sets a single setting in UserAccountControl. UserFlag must be a value from ADS_USER_FLAG dictionary keys. More information can be found at http://msdn.microsoft.com/en-us/library/aa772300.aspx. newValue accepts boolean values""" if userFlag not in ADS_USER_FLAG.keys(): raise InvalidValue("userFlag",userFlag,list(ADS_USER_FLAG.keys())) elif newValue not in (True, False): raise InvalidValue("newValue",newValue,[True,False]) else: # retreive the userAccountControl as if it didn't have the flag in question set. if self.get_attribute('userAccountControl',False) & ADS_USER_FLAG[userFlag] : nv = self.get_attribute('userAccountControl',False) ^ ADS_USER_FLAG[userFlag] else: nv = self.get_attribute('userAccountControl',False) # i f the flag is true, then the value is present and # we add it to the starting point with B-OR. # Otherwise, if it's false, it's just not present, # so we leave it without any mention of the flag as in previous step. if newValue: nv = nv | ADS_USER_FLAG[userFlag] self.update_attribute('userAccountControl',nv) def _disable(self): try: if self._ldap_adsi_obj.AccountDisabled == False: self._ldap_adsi_obj.AccountDisabled = True self._flush() except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) def _enable(self): try: if self._ldap_adsi_obj.AccountDisabled == True: self._ldap_adsi_obj.AccountDisabled = False self._flush() except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) def _get_password_last_set(self): # http://www.microsoft.com/technet/scriptcenter/topics/win2003/lastlogon.mspx # kudos to http://docs.activestate.com/activepython/2.6/pywin32/html/com/help/active_directory.html return pyadutils.convert_datetime(self.get_attribute('pwdLastSet',False)) def move(self, new_ou_object): """Moves the object to a new organizationalUnit. new_ou_object expects a ADContainer object where the current object will be moved to.""" try: new_ou_object._ldap_adsi_obj.MoveHere(('LDAP://'+self.dn),self.prefixed_cn) new_ou_object._flush() except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) new_dn = ','.join((self.prefixed_cn,new_ou_object.dn)) time.sleep(.5) self.__ads_path = pyadutils.generate_ads_path(new_dn, 'LDAP', self.default_ldap_server, self.default_ldap_port) self._ldap_adsi_obj = _adsi_provider.getObject('',self.__ads_path) self.__distinguished_name = self.get_attribute('distinguishedName',False) def rename(self, new_name, set_sAMAccountName=True): """Renames the current object within its current organizationalUnit. new_name expects the new name of the object (just CN not prefixed CN or distinguishedName).""" parent = self.get_parent_container() if self.type == 'organizationalUnit': pcn = 'ou=' else: pcn = 'cn=' pcn += new_name try: if self.type in ('user','computer','group') and set_sAMAccountName: self._ldap_adsi_obj.Put('sAMAccountName', new_name) parent._ldap_adsi_obj.MoveHere(('LDAP://'+self.dn), pcn) self._ldap_adsi_obj.GetInfoEx((distinguishedName, cn), 0) self.__distinguishedName = self.get_attribute('distinguishedName',False) except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) def add_to_group(self, group): """Adds current object to the specified group. group expects an ADGroup object.""" group.add_members(self) def remove_from_group(self, group): """Removes current object from the specified group. group expects an ADGroup object to which the current object belongs.""" group.remove_members(self) def set_managedby(self, user): """Sets managedBy on object to the specified user""" if user: assert manager.__class__.__str__ == 'ADUser' self.update_attribute('managedBy', user.dn) else: self.clear_attribute('managedBy') def clear_managedby(self): """Sets object to be managedBy nobody""" return self.set_manager(None) def dump_to_xml(self, whitelist_attributes=[], blacklist_attributes=[]): if len(whitelist_attributes) == 0: whitelist_attributes = self.get_allowed_attributes() attributes = list(set(whitelist_attributes) - set(blacklist_attributes)) doc = xml.Document() adobj_xml_doc = doc.createElement("ADObject") adobj_xml_doc.setAttribute("objectGUID", str(self.guid).lstrip('{').rstrip('}')) adobj_xml_doc.setAttribute("pyADType", self.type) doc.appendChild(adobj_xml_doc) for attribute in attributes: node = doc.createElement("attribute") node.setAttribute("name", attribute) value = self.get_attribute(attribute,False) if str(type(value)).split("'",2)[1] not in ('buffer','instance') and value is not None: if type(value) is not list: try: ok_elem=True node.setAttribute("type", str(type(value)).split("'",2)[1]) try: text = doc.createTextNode(str(value)) except: text = doc.createTextNode(value.encode("latin-1", 'replace')) node.appendChild(text) except: print 'attribute: %s not xml-able' % attribute else: node.setAttribute("type", "multiValued") ok_elem = False try: for item in value: if str(type(item)).split("'",2)[1] not in ('buffer','instance') and value is not None: valnode = doc.createElement("value") valnode.setAttribute("type", str(type(item)).split("'",2)[1]) text = doc.createTextNode(str(item)) valnode.appendChild(text) node.appendChild(valnode) ok_elem=True except: print 'attribute: %s not xml-able' % attribute if ok_elem: adobj_xml_doc.appendChild(node) return doc.toxml(encoding="UTF-8") def adjust_pyad_type(self): """Adjusts pyAD class to match object pyad type.""" if self.type in self._py_ad_object_mappings.keys(): self.__class__ = self._py_ad_object_mappings[self.type] else: raise Exception("Unkown type. Adjustment not possible.") def __get_parent_container(self): q = ADObject.from_dn(self.parent_container_path, options={'server':self.default_ldap_server,'port':self.default_ldap_port}) q.adjust_pyad_type() return q parent_container = property(__get_parent_container)pyad/pyad/._adquery.py000644 000765 000024 00000000341 11615410022 015462 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/pyad/adquery.py000644 000765 000024 00000003461 11615410022 015253 0ustar00zakirstaff000000 000000 from adbase import * import pyadutils class ADQuery(ADBase): def __init__(self): self.__adodb_conn = win32com.client.Dispatch("ADODB.Connection") self.__adodb_conn.Open("Provider=ADSDSOObject") self.reset() def reset(self): self.__rs = self.__rc = None self.__queried = False def execute_query(self, attributes=["distinguishedName"], where_clause=None, type="LDAP", base_dn=None, server=None, port=None): if not base_dn: if type == "LDAP": base_dn = self.default_domain if type == "GC": base_dn = default_forest query = "SELECT %s FROM '%s'" % (','.join(attributes), pyadutils.generate_ads_path(base_dn, type, server, port)) if where_clause: query = ' '.join((query, 'WHERE', where_clause)) self.__rs,self.__rc = self.__adodb_conn.Execute(query) self.__queried = True def get_row_count(self): return self.__rs.RecordCount def get_single_result(self): if self.get_row_count() != 1: raise invalidResults(self.get_row_count()) self.__rs.MoveFirst() d = {} for f in self.__rs.Fields: d[f.Name] = f.Value return d def get_results(self): if not self.__queried: raise noExecutedQuery if not self.__rs.EOF: self.__rs.MoveFirst() while not self.__rs.EOF: d = {} for f in self.__rs.Fields: d[f.Name] = f.Value yield d self.__rs.MoveNext() def get_all_results(self): if not self.__queried: raise noExecutedQuery l = [] for d in self.get_results(): l.append(d) return lpyad/pyad/._adsearch.py000644 000765 000024 00000000341 11615375004 015574 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/pyad/adsearch.py000644 000765 000024 00000002451 11615375004 015363 0ustar00zakirstaff000000 000000 from adquery import * from adbase import * _ad_query_obj = ADQuery() def by_cn(cn, search_base=None, options={}): if not search_base: search_base = ADBase.default_domain _ad_query_obj.reset() _ad_query_obj.execute_query(where_clause=("CN = '%s'" % cn), base_dn=search_base, server=options.get("server"), port=options.get("port"), type="GC") return _ad_query_obj.get_single_result()['distinguishedName'] def by_upn(upn, search_base=None, options={}): if not search_base: search_base = ADBase.default_forest _ad_query_obj.reset() _ad_query_obj.execute_query(where_clause=("userPrincipalName = '%s'" % upn), base_dn=search_base, type="GC", server=options.get("server"), port=options.get("port")) return _ad_query_obj.get_single_result()['distinguishedName'] def by_sid(sid, search_base=None, options={}): if not search_base: search_base = ADBase.default_domain _ad_query_obj.reset() _ad_query_obj.execute_query(where_clause=("objectSid = '%s'" % sid), base_dn=search_base, server=options.get("server"), port=options.get("port"), type="GC") return _ad_query_obj.get_single_result()['distinguishedName']pyad/pyad/._aduser.py000644 000765 000024 00000000441 11615375770 015320 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/aduser.py000644 000765 000024 00000002263 11615375770 015107 0ustar00zakirstaff000000 000000 from adobject import * from adsearch import _ad_query_obj class ADUser(ADObject): @classmethod def create(cls, name, container_object, password=None, upn_suffix=None, enable=True, optional_attributes={}): return container_object.create_user(name=name, password=password, upn_suffix=upn_suffix, enable=enable, optional_attributes=optional_attributes) def set_password(self, password): try: self._ldap_adsi_obj.SetPassword(password) self._flush() except pywintypes.com_error, excpt: pyadutils.pass_up_com_exception(excpt) def force_pwd_change_on_login(self): self.update_attribute('PwdLastSet',0) def grant_password_lease(self): self.update_attribute('PwdLastSet',-1) def get_password_last_set(self): """Returns datetime object of when user last reset their password.""" return self._get_password_last_set() def set_expiration(self, dt): self._ldap_adsi_obj.AccountExpirationDate = dt self._flush() ADObject._py_ad_object_mappings['user'] = ADUserpyad/pyad/._pyad.py000644 000765 000024 00000000441 11615374246 014767 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/pyad.py000644 000765 000024 00000000544 11615374246 014556 0ustar00zakirstaff000000 000000 from adobject import * import aduser, adcomputer, addomain, addomain, adgroup, adobject, pyadconstants, adcontainer def pyAD(distinguished_name, options={}): q = ADObject(distinguished_name=distinguished_name,options=options) if q.type in __py_ad_object_mappings.keys(): q.__class__ = __py_ad_object_mappings[q.type] return q pyad/pyad/._pyadconstants.py000644 000765 000024 00000000441 11615374250 016717 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/pyadconstants.py000644 000765 000024 00000012200 11615374250 016476 0ustar00zakirstaff000000 000000 from pyadexceptions import * # http://msdn.microsoft.com/en-us/library/aa772263(VS.85).aspx ADS_GROUP_TYPE = { 'GLOBAL':0x2, 'LOCAL':0x4, 'UNIVERSAL':0x8, 'SECURITY_ENABLED':-0x80000000} # http://msdn.microsoft.com/en-us/library/aa772300.aspx ADS_USER_FLAG = { 'SCRIPT':0x1, 'ACCOUNTDISABLE':0x2, 'HOMEDIR_REQUIRED':0x8, 'LOCKOUT':0x10, 'PASSWD_NOTREQD':0x20, 'PASSWD_CANT_CHANGE':0x40, 'ENCRYPTED_TEXT_PASSWORD_ALLOWED':0x80, 'TEMP_DUPLICATE_ACCOUNT':0x100, 'NORMAL_ACCOUNT':0x200, 'INTERDOMAIN_TRUST_ACCOUNT':0x800, 'WORKSTATION_TRUST_ACCOUNT':0x1000, 'SERVER_TRUST_ACCOUNT':0x2000, 'DONT_EXPIRE_PASSWD':0x10000, 'MNS_LOGON_ACCOUNT':0x20000, 'SMARTCARD_REQUIRED':0x40000, 'TRUSTED_FOR_DELEGATION':0x80000, 'NOT_DELEGATED':0x100000, 'USE_DES_KEY_ONLY':0x200000, 'DONT_REQUIRE_PREAUTH':0x400000, 'PASSWORD_EXPIRED':0x800000, 'TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION':0x1000000} PYAD_CATEGORY_TYPE_OVERRIDE_MAPPPINGS = { "Person":"user", "Organizational-Unit":"organizationalUnit", "Domain-DNS":"domain"} WIN32_ERRORS = { 0x80072030:InvalidObjectException, 0x80072020:InvalidObjectException} # http://msdn.microsoft.com/en-us/library/aa705940(VS.85).aspx GENERIC_ADSI_ERRORS = { 0x00005011L:('S_ADS_ERRORSOCCURRED','During a query, one or more errors occurred.','Verify that the search preference can be legally set and, if so, that it is properly set.',win32Exception), 0x00005012L:('S_ADS_NOMORE_ROWS','The search operation has reached the last row.','Move on to the rest of the program.',win32Exception), 0x00005013L:('S_ADS_NOMORE_COLUMNS','The search operation has reached the last column for the current row.','Move on to next row.',win32Exception), 0x80005000L:('E_ADS_BAD_PATHNAME','An invalid ADSI pathname was passed.','Verify that the object exists on the directory server and check for typographic errors of the path.',win32Exception), 0x80005001L:('E_ADS_INVALID_DOMAIN_OBJECT','An unknown ADSI domain object was requested','Verify the path of the domain object.',win32Exception), 0x80005002L:('E_ADS_INVALID_USER_OBJECT','An unknown ADSI user object was requested.','Verify the existence of the user object, check for typos of the path and the user access right',win32Exception), 0x80005003L:('E_ADS_INVALID_COMPUTER_OBJECT','An unknown ADSI computer object was requested.','Verify the existence of the computer object, check for typos of the path and the computer access rights.',win32Exception), 0x80005004L:('E_ADS_UNKNOWN_OBJECT','An unknown ADSI object was requested.','Verify the name of and the access rights to the object.',win32Exception), 0x80005005L:('E_ADS_PROPERTY_NOT_SET','The specified ADSI property was not set.','',win32Exception), 0x80005006L:('E_ADS_PROPERTY_NOT_SUPPORTED','The specified ADSI property is not supported.','Verify that the correct property is set.',win32Exception), 0x80005007L:('E_ADS_PROPERTY_INVALID','The specified ADSI property is invalid.','Verify that the search preference can be legally set and, if so, that it is properly set.',win32Exception), 0x80005008L:('E_ADS_BAD_PARAMETER','One or more input parameters are invalid.','Verify that the search preference can be legally set and, if so, that it is properly set.',win32Exception), 0x80005009L:('E_ADS_OBJECT_UNBOUND','The specified ADSI object is not bound to a remote resource.','Call GetInfo on a newly created object after SetInfo has been called.',win32Exception), 0x8000500AL:('E_ADS_PROPERTY_NOT_MODIFIED','The specified ADSI object has not been modified','',win32Exception), 0x8000500BL:('E_ADS_PROPERTY_MODIFIED','The specified ADSI object has been modified.','',win32Exception), 0x8000500CL:('E_ADS_CANT_CONVERT_DATATYPE','The data type cannot be converted to/from a native DS data type.','Verify that the correct data type is used and/or that there is sufficient schema data available to perform data type conversion.',win32Exception), 0x8000500DL:('E_ADS_PROPERTY_NOT_FOUND','The property cannot be found in the cache.','Verify that attribute exists for particular object.',win32Exception), 0x8000500EL:('E_ADS_OBJECT_EXISTS','The ADSI object already exists.','Use a different name to create the object.',win32Exception), 0x8000500FL:('E_ADS_SCHEMA_VIOLATION','The attempted action violates the directory service schema rules.','',win32Exception), 0x80005010L:('E_ADS_COLUMN_NOT_SET','The specified column in the ADSI was not set.','',win32Exception), 0x80005014L:('E_ADS_INVALID_FILTER','The specified search filter is invalid.','Use the correct format of the filter accepted by the directory server.',win32Exception)} # http://msdn.microsoft.com/en-us/library/aa705941(VS.85).aspx GENERIC_COM_ERRORS = { 0x80004004:('E_ABORT','Operation aborted.'), 0x80004005:('E_FAIL','Unspecified error.'), 0x80004002:('E_NOINTERFACE','Interface not supported.'), 0x80004001:('E_NOTIMPL','Not implemented.'), 0x80004003:('E_POINTER','Invalid pointer.'), 0x8000FFFF:('E_UNEXPECTED','Catastrophic failure.')} pyad/pyad/._pyadexceptions.py000644 000765 000024 00000000441 11615374250 017064 0ustar00zakirstaff000000 000000 Mac OS X  2!ATTR!iIcom.apple.quarantine com.macromates.caretq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailxR<[k0?'3/«pyad/pyad/pyadexceptions.py000644 000765 000024 00000004001 11615374250 016643 0ustar00zakirstaff000000 000000 class comException(Exception): def __init__(self, error_info, additional_info={}): self.error_info = error_info self.additional_info = additional_info class genericADSIException(comException): def __init__(self, error_info, additional_info={}): comException.__init__(error_info, additional_info) def __str__(self): return "%s (%s): %s" % (self.error_info['error_constant'], self.error_info['error_code'], self.error_info['error_message']) class win32Exception(comException): def __init__(self, error_info, additional_info={}): comException.__init__(self, error_info, additional_info) def __str__(self): return "%s: %s" % (self.error_info['error_code'], self.error_info['message']) class invalidOwnerException(Exception): def __str__(self): return "The submitted object is not eligible to own another object." class noObjectFoundException(Exception): def __str__(self): return "The requested object does not exist." class InvalidObjectException(noObjectFoundException, win32Exception): def __init__(self, error_info, additional_info): win32Exception.__init__(self, error_info, additional_info) class InvalidAttribute(AttributeError): def __init__(self, obj, attribute): self.obj, self.attribute = obj, attribute def __str__(self): 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) class noExecutedQuery(Exception): def __str__(self): return 'No query has been executed. Therefore there are no results to return. Execute a query before requesting results.' class invalidResults(Exception): def __init__(self, numberResults): self.__numberResults = numberResults def __str__(self): return 'The specified query resturned %i results. getSingleResults only functions with a single result.' % self.__numberResultspyad/pyad/._pyadutils.py000644 000765 000024 00000000341 11615374250 016042 0ustar00zakirstaff000000 000000 Mac OS X  2ATTRIIcom.apple.quarantineq/0001;4e41c3ff;Mail;F7E9A1FF-EFF7-4D9A-B24A-E1F962E4796F|com.apple.mailpyad/pyad/pyadutils.py000644 000765 000024 00000012235 11615374250 015632 0ustar00zakirstaff000000 000000 from adbase import * def convert_error_code(error_code): return error_code % 2 ** 32 def interpret_com_exception(excp, additional_info={}): #expects the actualy pywintypes.com_error exception that's thrown... d = {} d['error_num'] = convert_error_code(excp.args[2][5]) # for some reason hex() includes the L for long in the hex... however since it's a string, we don't care... since L would never be in a hex code, we can safely just remove it. d['error_code'] = hex(d['error_num']).rstrip('L') if d['error_code'][0:7] == '0x80005': if d['error_num'] in GENERIC_ADSI_ERRORS.keys(): d['exception_type'] = 'known_generic_adsi_error' d['error_constant'] = GENERIC_ADSI_ERRORS[d['error_num']][0] d['message'] = ' '.join(GENERIC_ADSI_ERRORS[d['error_num']][1:3]) else: # this supposedly should not happen, but I'd rather be ready for the case that Microsoft made a typo somewhere than die weirdly. d['error_constant'] = None d['exception_type'] = 'unknown_generic_adsi_error' d['message'] = 'unknown generic ADSI error' d['exception'] = genericADSIException elif d['error_code'][0:6] == '0x8007': d['exception_type'] = 'win32_error' d['error_constant'] = None d['message'] = win32api.FormatMessage(d['error_num']) # returns information about error from winerror.h file... elif d['error_num'] in GENERIC_COM_ERRORS.keys(): d['exception_type'] = 'generic_com_error' d['error_constant'] = GENERIC_COM_ERRORS[d['error_num']][0] d['message'] = GENERIC_COM_ERRORS[d['error_num']][1] else: d['exception_type'] = 'unknown' d['error_constant'] = None d['message'] = excp.args[2][4] d['additional_info'] = additional_info={} return d def pass_up_com_exception(excp, additional_info={}): if excp.__class__ in (genericADSIException, comException, win32Exception): raise excp else: info = interpret_com_exception(excp) type_ = info['exception_type'] if type_ == 'win32_error': # raise exception defined in WIN32_ERRORs if there is one... otherwise, just raise a generic win32Exception raise WIN32_ERRORS.get(info['error_num'], win32Exception)(error_info=info, additional_info=additional_info) elif type_ == 'known_generic_adsi_error': raise GENERIC_ADSI_ERRORS[info['error_num']][3](error_info=info, additional_info=additional_info) elif type_ == 'unknown_generic_adsi_error': raise genericADSIException(error_info=info, additional_info=additional_info) else: raise comException(error_info=info, additional_info=additional_info) def convert_datetime(adsi_time_com_obj): """Converts 64-bit integer COM object representing time into a python datetime object.""" # credit goes to John Nielsen who documented this at http://docs.activestate.com/activepython/2.6/pywin32/html/com/help/active_directory.html. return datetime.datetime.fromtimestamp((((long(adsi_time_com_obj.highpart) << 32)\ + long(adsi_time_com_obj.lowpart)) - 116444736000000000L)/10000000) def convert_guid(guid_object): return pywintypes.IID(guid_object, True) def convert_sid(sid_object): return pywintypes.SID(sid_object) def generate_list(input): if type(input) is list: return input elif type(input) in (set,tuple): return list(input) else: return [input,] def escape_path(path): escapes = ( ('\\','\\5c'), ('*','\\2a'), ('(','\\28'), (')','\\29'), ('/','\\2f'), (chr(0),'\\00') ) for char, escape in escapes: path = path.replace(char, escape) return path def generate_ads_path(distinguished_name, type_, server=None, port=None): """Generates a proper ADsPath to be used when connecting to an active directory object or when searching active directory. Keyword arguments: - distinguished_name: DN of object or search base such as 'cn=zakir,ou=users,dc=mycompany,dc=com' (required). - type: 'GC' (global-catalog) or 'LDAP' to determine what directory to be searched (required). - server: FQDN of domain controller if necessary to connect to a particular server (optional unless port is defined). - port: port number for directory service if not default port. If port is specified, server must be specified (optional).""" if type_ == "LDAP": server = server if server else ADBase.default_ldap_server port = port if port else ADBase.default_ldap_port elif type_ == "GC": server = server if server else ADBase.default_gc_server port = port if port else ADBase.default_gc_port else: raise Exception("Invalid type specified.") ads_path = ''.join((type_,'://')) if server: ads_path = ''.join((ads_path,server)) if port: ads_path = ':'.join((ads_path,str(port))) ads_path = ''.join((ads_path,'/')) ads_path = ''.join((ads_path,escape_path(distinguished_name))) return ads_pathpyad/.git/branches/000755 000765 000024 00000000000 11652270270 014726 5ustar00zakirstaff000000 000000 pyad/.git/COMMIT_EDITMSG000644 000765 000024 00000000031 11652315003 015214 0ustar00zakirstaff000000 000000 correcting readme syntax pyad/.git/config000644 000765 000024 00000000422 11652271012 014322 0ustar00zakirstaff000000 000000 [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true [remote "origin"] url = git@github.com:zakird/pyad.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master pyad/.git/description000644 000765 000024 00000000111 11652270270 015400 0ustar00zakirstaff000000 000000 Unnamed repository; edit this file 'description' to name the repository. pyad/.git/HEAD000644 000765 000024 00000000027 11652270270 013564 0ustar00zakirstaff000000 000000 ref: refs/heads/master pyad/.git/hooks/000755 000765 000024 00000000000 11652270270 014264 5ustar00zakirstaff000000 000000 pyad/.git/index000644 000765 000024 00000003340 11652315003 014165 0ustar00zakirstaff000000 000000 DIRCNNb Ã\[gbBqu% README.rstNBN5"3s2/E6/"pyad/__init__.pyN NA"6̡m<+e $pyad/adbase.pyNN"3@H>US[Ǒ63pyad/adcomputer.pyN N5"3 rbA`'bPOpeVpyad/adcontainer.pyN N5"3SGfo>"pyad/addomain.pyN N5"3ځMcmһ9Epyad/adgroup.pyNN6t"3NE{)VKv@$Gp&)pyad/adobject.pyNBN6"31tA/YT$!oCqѤH;pyad/adquery.pyNBN5"3)') %pyad/adsearch.pyNN5"3n|\>A%U!ʛdpyad/aduser.pyNN5"3d*뺮& O pyad/pyad.pyNN5"3ycvEeEڳpyad/pyadconstants.pyN N5"37Ex*.wI!5pyad/pyadexceptions.pyNBN5"3+Ps벻e\~F pyad/pyadutils.pyNRNRbS@s`Mt9>^msetup.pyNAN6 "3Zi[{nV ^_<(Kvtest_handler.pyNAN6 "37gI*t zو4Ltests/__init__.pyNN6 "3eyɂO46Cg(ZNtests/pyadunittest.pyNN6 "3W.BڄZ.?P32tests/tests_adbase.pyNN6<"3 B)Ê8E ~tests/tests_adquery.py E$7feaepyad/.git/info/000755 000765 000024 00000000000 11652270270 014074 5ustar00zakirstaff000000 000000 pyad/.git/logs/000755 000765 000024 00000000000 11652270354 014110 5ustar00zakirstaff000000 000000 pyad/.git/objects/000755 000765 000024 00000000000 11652315003 014564 5ustar00zakirstaff000000 000000 pyad/.git/refs/000755 000765 000024 00000000000 11652270370 014101 5ustar00zakirstaff000000 000000 pyad/.git/refs/heads/000755 000765 000024 00000000000 11652315003 015156 5ustar00zakirstaff000000 000000 pyad/.git/refs/remotes/000755 000765 000024 00000000000 11652270370 015557 5ustar00zakirstaff000000 000000 pyad/.git/refs/tags/000755 000765 000024 00000000000 11652270724 015042 5ustar00zakirstaff000000 000000 pyad/.git/refs/tags/0.4.9000644 000765 000024 00000000051 11652270724 015431 0ustar00zakirstaff000000 000000 d200389609b5394f7e73db029fb0e016984c7a7b pyad/.git/refs/remotes/origin/000755 000765 000024 00000000000 11652315010 017035 5ustar00zakirstaff000000 000000 pyad/.git/refs/remotes/origin/master000644 000765 000024 00000000051 11652315010 020247 0ustar00zakirstaff000000 000000 25c507e69f55aba21544dc39ec18c37291f4e45c pyad/.git/refs/heads/master000644 000765 000024 00000000051 11652315003 016370 0ustar00zakirstaff000000 000000 25c507e69f55aba21544dc39ec18c37291f4e45c pyad/.git/objects/15/000755 000765 000024 00000000000 11652312000 015003 5ustar00zakirstaff000000 000000 pyad/.git/objects/22/000755 000765 000024 00000000000 11652312531 015012 5ustar00zakirstaff000000 000000 pyad/.git/objects/25/000755 000765 000024 00000000000 11652315003 015012 5ustar00zakirstaff000000 000000 pyad/.git/objects/27/000755 000765 000024 00000000000 11652270306 015022 5ustar00zakirstaff000000 000000 pyad/.git/objects/2a/000755 000765 000024 00000000000 11652270321 015071 5ustar00zakirstaff000000 000000 pyad/.git/objects/2b/000755 000765 000024 00000000000 11652270306 015075 5ustar00zakirstaff000000 000000 pyad/.git/objects/2e/000755 000765 000024 00000000000 11652270334 015101 5ustar00zakirstaff000000 000000 pyad/.git/objects/30/000755 000765 000024 00000000000 11652314627 015021 5ustar00zakirstaff000000 000000 pyad/.git/objects/3b/000755 000765 000024 00000000000 11652312553 015077 5ustar00zakirstaff000000 000000 pyad/.git/objects/46/000755 000765 000024 00000000000 11652312132 015015 5ustar00zakirstaff000000 000000 pyad/.git/objects/5c/000755 000765 000024 00000000000 11652270354 015104 5ustar00zakirstaff000000 000000 pyad/.git/objects/65/000755 000765 000024 00000000000 11652314627 015031 5ustar00zakirstaff000000 000000 pyad/.git/objects/67/000755 000765 000024 00000000000 11652270334 015027 5ustar00zakirstaff000000 000000 pyad/.git/objects/72/000755 000765 000024 00000000000 11652270306 015022 5ustar00zakirstaff000000 000000 pyad/.git/objects/74/000755 000765 000024 00000000000 11652270306 015024 5ustar00zakirstaff000000 000000 pyad/.git/objects/79/000755 000765 000024 00000000000 11652270334 015032 5ustar00zakirstaff000000 000000 pyad/.git/objects/7f/000755 000765 000024 00000000000 11652270306 015106 5ustar00zakirstaff000000 000000 pyad/.git/objects/86/000755 000765 000024 00000000000 11652312132 015021 5ustar00zakirstaff000000 000000 pyad/.git/objects/8a/000755 000765 000024 00000000000 11652270306 015102 5ustar00zakirstaff000000 000000 pyad/.git/objects/8e/000755 000765 000024 00000000000 11652270312 015103 5ustar00zakirstaff000000 000000 pyad/.git/objects/9a/000755 000765 000024 00000000000 11652270306 015103 5ustar00zakirstaff000000 000000 pyad/.git/objects/9d/000755 000765 000024 00000000000 11652270354 015111 5ustar00zakirstaff000000 000000 pyad/.git/objects/a1/000755 000765 000024 00000000000 11652270306 015073 5ustar00zakirstaff000000 000000 pyad/.git/objects/a3/000755 000765 000024 00000000000 11652315003 015067 5ustar00zakirstaff000000 000000 pyad/.git/objects/ad/000755 000765 000024 00000000000 11652315003 015150 5ustar00zakirstaff000000 000000 pyad/.git/objects/b6/000755 000765 000024 00000000000 11652312132 015073 5ustar00zakirstaff000000 000000 pyad/.git/objects/b8/000755 000765 000024 00000000000 11652270317 015105 5ustar00zakirstaff000000 000000 pyad/.git/objects/bd/000755 000765 000024 00000000000 11652270306 015157 5ustar00zakirstaff000000 000000 pyad/.git/objects/c1/000755 000765 000024 00000000000 11652314627 015102 5ustar00zakirstaff000000 000000 pyad/.git/objects/d2/000755 000765 000024 00000000000 11652270724 015103 5ustar00zakirstaff000000 000000 pyad/.git/objects/d4/000755 000765 000024 00000000000 11652270306 015101 5ustar00zakirstaff000000 000000 pyad/.git/objects/df/000755 000765 000024 00000000000 11652312531 015160 5ustar00zakirstaff000000 000000 pyad/.git/objects/e3/000755 000765 000024 00000000000 11652270354 015104 5ustar00zakirstaff000000 000000 pyad/.git/objects/e6/000755 000765 000024 00000000000 11652312553 015105 5ustar00zakirstaff000000 000000 pyad/.git/objects/e8/000755 000765 000024 00000000000 11652312000 015072 5ustar00zakirstaff000000 000000 pyad/.git/objects/ea/000755 000765 000024 00000000000 11652312000 015143 5ustar00zakirstaff000000 000000 pyad/.git/objects/ee/000755 000765 000024 00000000000 11652270306 015163 5ustar00zakirstaff000000 000000 pyad/.git/objects/f4/000755 000765 000024 00000000000 11652270306 015103 5ustar00zakirstaff000000 000000 pyad/.git/objects/fe/000755 000765 000024 00000000000 11652270354 015167 5ustar00zakirstaff000000 000000 pyad/.git/objects/info/000755 000765 000024 00000000000 11652270270 015525 5ustar00zakirstaff000000 000000 pyad/.git/objects/pack/000755 000765 000024 00000000000 11652270270 015510 5ustar00zakirstaff000000 000000 pyad/.git/objects/fe/4a3793d6891c7b6c0955e1a58a172c400749ec000444 000765 000024 00000000713 11652270354 022111 0ustar00zakirstaff000000 000000 x+)JMU055f040031Q,+dzJhۛ2-f7P*KLIJ,N)Z8{6۵NgUq\Qr~nAiIjHaߞ=>>>lg")+Ĩ,Jzq/I|w5U rW*{ZWS,(]}gͽ۲upUIY% e_5z9lV9^HM4=Ց!*΅=//.񰆫*NM,J)Su/M)E_^yb/t9n_PSO@T&̙u]uwM\S$$Ծ_S2k̮nmFRZZP R蒊Zz垬M?LeFR\ZV}8`iכvZz"΍kJ*pyad/.git/objects/f4/6e8d7c885c3e41b7f0ebe3fd255521ca9bca64000444 000765 000024 00000001032 11652270306 022456 0ustar00zakirstaff000000 000000 xTM 9R%NZmHުvՏSB1 /;vvK9h>of9w^δ s lqH^/s +h_. de\}6Fdp ʗDJF&5Fqb>Ե<`ͅ5LQ! ?#D_Se*4m'SMyi]?6m tMyZHcu2uy7ZytLOͤ ~s8WiKj'7HԀ&I$!DG Hwea BMa6 JU$%p.[qn}l?\FEm^\:mRu*~!C;|pyad/.git/objects/ee/0faf03e08915796376ec4565fed80345d2dab3000444 000765 000024 00000003612 11652270306 022246 0ustar00zakirstaff000000 000000 xXKSHsG`LO` L3DAHѶIQˠY-ɀwcfVd982+|q)y32_ )DUb6!?sof:6>+dryؗ//?ǧWNY=rl4̒% %?}zk7yS[?lwF~z>];1swϽ^؍o v =oqXM qalrGGEYzB^0g[AwV0T.n`Ga:Y~K2u}`w‰; 3' }϶73N(zP@寿^zAFY^%Q'r֥}+sHW+4o6;T1S:Qg>5N"YQb[\( eC_o] ԾVH:CPB:ξ GUI( ,\+MPJ餺TjumMhGA_# -'iz3slda_Qe-EO*pnsZ a+NDqĤLmwwBްs&8m% Nfj3Og/(ٸX(@րVv ;@ruV`d5DŽaa D&G_4ÖkY4hۦ%kNl]4~jc%4di "DkuyQVЁmӱ Gl߹#+/zπ|6`9ޜK!Jhz+䛜Eq5ZEg8ɏr9~o۝m<<_ͭj/ӢĹu61Z7˨ Pqho H~{,g[t~!O Y䫋.3иR Q-Rf`8 :eP**q+ް}noF0|W [J#-u'sQ(4o, nOe Si!a|P ;pyad/.git/objects/ea/2903adc38ae6917ff406913845207ec9ecfd0f000444 000765 000024 00000001026 11652270334 022323 0ustar00zakirstaff000000 000000 xVMo1`[V)"ڪVBhevĊ#oC} ^HBBڔr@yo޼ =|X4&I ( THjBNDm ^(sn`I4GF$g5j],:SFyކiȭu<+LTv,rTu09?^|{2V2 %ٷW P`s_porʋK:נLqɱq\ȒK(C@[_ !,McoY4*rFYڂ Wl4<~ډz_ J)B*٪-׊JB9+o&.g -,4#$tŝe\2 E&*gv<ߓX˴MH*HKުLLUkL; Ƶ`MDS{slX3fJ#(#$i(YUk ^ ۵ 2+=Zzڎ{]L3Npyad/.git/objects/ea/be6762c0fc9c31731d0c20c911226fbb989f7a000444 000765 000024 00000001110 11652312000 022262 0ustar00zakirstaff000000 000000 xTj@06$r݄B *(RZZIv{qpK+9s;gP;X.aqxTUkЅx~9/p j>_G@#Ai.gF*zVx2r4?KOQSmj7mfmŀ#'E%}t}̻Jh `6f(5eL=l*v7M9mWEqю8_/63]b OGZNf3w1ȥ9ę 9B$+yMQ( v)+!N*|Y>k ̸ _zJn[1ǰp@%rb q,,`W+b8iX{ڿ;qe:׻8y;\&X yI\(uy \%riBɺۗ%ڨqJ6.R&m`Fm\b$~ @i"5'eTOONNz]#Rbpyad/.git/objects/ea/dd0ecfda814d639bfa8d6dd2bb398445f5a6ae000444 000765 000024 00000004164 11652270306 022764 0ustar00zakirstaff000000 000000 xYYoFsR 8E+r%UҎwf|d;|3;^J|7JȼY2#m:#>~წZӮ䂶㚀ϷRfM"4j:T,6UZ&*ܥR뮇:kZ_%68QFu[@%ai;a:y> F6 ݜ XIr]`>:)7oPelX|p3$ڮɔ*nڍ3T{h>f+Cx:FfM_LGVAJ5MlbT-TW7ЍXpCռ#!b"EQ0JM eEkTwX[6b) :G('o[ ;ZXC(& :=eh, q4p[s*XdH\Ƨ'M:!,*Qo 0pAxx".i &,&v|;a s5k6!ʩ?\#2g'Fc"P@x r sXC,2(zP>ạh]Q۴mھo Vů@<BWa%0VJ|ߣ@usaMy\ǨiH_\ƴ܉KfE"-x; DΏ(!"3@eLv\+<)`,xZ^2y2')]p zBA)AlݨIy s m 0Jv U>h_ލK#Hl/AExKoұ9\ÂenEO~@^=TECiWaoW@9X"D< ~y]"?yLXr | y$c:cj1`q0?A=TފtZ?.L:hi=rml~?L.=0jk/ *#" rSKV11ZքH89L j4ֈţ.,>P꘶ Qva&jX l t$Q )OWn2kן?~NXpB=V{d^qoY{1qauQ/UwUapyad/.git/objects/e6/651ff46b84107848ad4a961cb53b813dd5552e000444 000765 000024 00000000266 11652312553 022110 0ustar00zakirstaff000000 000000 x+)JMU00`040031Qrutu+*.a{ϼ5+l̴U[&Ih6171^擯uTp>\%jHqjIi^A%C[qs%]6]3$$>#1/%'tGf4{0w67jc.RY );^>cxjfu4]Hpyad/.git/objects/e3/d79833944bf00b88e9e600e8aab1298eac27ae000444 000765 000024 00000000235 11652270354 022327 0ustar00zakirstaff000000 000000 x+)JMU04d040075U,+dH_")G7;L| *SJ KRK@J+l71s~.!UYTT)HIq|bJRbq*HwFGoD'm=MiaijQ%H+M浇M6U䛿$Erpyad/.git/objects/df/7f497d23b5d0eec8072866d98f5176988db4a5000444 000765 000024 00000000224 11652312531 022214 0ustar00zakirstaff000000 000000 x+)JMU041b01^擯uTp>\%D8@9}ˮ m USZ\ZR#3]^B\;51zAl,fx|}/1XQo:Kk:pyad/.git/objects/d4/dd1a53ea47666ff0ffd400e413cf3e228fb5e9000444 000765 000024 00000000575 11652270306 022462 0ustar00zakirstaff000000 000000 xmR;k0l8nL-.KZ KNlIHrJV%Z qm[UeRX;%8%dza=1ݗŽam- <(I7f{- =0"t bp,on)QPñ'fE#?uR!RoY6`4\?rVhc5W)Eg"qx7dF >9ӡKVR0ÿAJ@.6E+?N¨\5z hTUb*&\ S~#wbP79$*9Gq#{l-j[L'uBܚ}|;;FAmw<_n*pyad/.git/objects/d2/00389609b5394f7e73db029fb0e016984c7a7b000444 000765 000024 00000000210 11652270724 022020 0ustar00zakirstaff000000 000000 x- !@}ydt袧nY] 3s:gPME8ZHhOAHusʓbg&K>(9m5A<.Gu5)rYaQWjϲZLugå]:oT27'[bٵau;?sjJRK3RrR@JwdFW Sg3Fy]6\q(f;3͛Qى^~UuRPj_V3ӸZ\^}jl g:S]RHjfƱ_8l0ۑ :k(Yu>bNǩ8`i2Ahr*-=m9{a6Wjs*.N.Lj*da w=R<{OPsv$ e<[՗5 oXi` Ѐ[CĎCA-JoR=#qRg9JT-3=piHKJjJֹ3$x;%+{i\( T4j 2;]E\S gDaEuTjqS1cx tљuNP!,'YC&C+"أhāFHj ~\P2g>X|Sw'8"Dl|rF<;BS¿ l+0ؼ&yb&FHuWF:FĈ6͖ɪpyad/.git/objects/b8/695b077bee6e56205e5fee3c9fb1289e014b76000444 000765 000024 00000000130 11652270317 022252 0ustar00zakirstaff000000 000000 xKOR4`H+U(LLQ-/*Q# re)%+*&fǫ[r)Ai^f HHXCpyad/.git/objects/b6/c036b9af259e70bff6c08a83b006d1f779499c000444 000765 000024 00000000262 11652312132 022243 0ustar00zakirstaff000000 000000 x+)JMU047c040031Qrutue{ϼ5+l̴U[&Ih6171^擯uTp>\%j@qjIi^A%C[qs%]6]3$$>#1/%'tGf4{0w67jc.RY );^>cxjfu%G?pyad/.git/objects/ad/6a4a573bb3170f20291a9765686f4df658cf65000444 000765 000024 00000000266 11652315003 022105 0ustar00zakirstaff000000 000000 x+)JMU00`040031Qrutu+*.aX|y̤I;m),8T^TS>8;sıgxYs*:mc$g6V^Pe D9oպ'3c)3TeYF[]DǵsgoSkik4`Yu΃E&m35[#Zq*G&PL|W9]y.Mv  Ǖ0--;r(ƚ ;B G4#@&CFkn XJEt^EtI\\pX:(h~E"lT*S|uw&Th%8'c?"nNF0Кvf iP~X mH ԋ[4hE]r 5"BBܪ{:va+`kxz ֵ\+5%FMނGR̈"4P<:Yys jefr s ї~>`DZ{MлLsW&!,q^Zl9ѓ<@#f ̈́gQ.k  jRȈ_EtۉafkJ%>aQ`q9Cx|/7"u:䤮{}hZxkx٢e"fb81܆46w꭫0?<]O'wuX-&#l:t&lrW`<>u|cpglPz+9`*/Uce*(/M#|*yTZ؅E7<ի5hN; \m.Ycrǣzq`e0E`FSib~&F¬LZ x4K}KpA 4xmT KwoZG[\#yn:u6˗㯕2s(k% 64Dd^!#^;a +RwBbyo,i'fتveK{V|.<ǏL| .&_sGv>Q7s$?AZ߉0N F48p>d \tTtNÖprh^G͵$mϡ*R>k#-L^8>:pyad/.git/objects/a1/9b96ff15f06d3cb72bdf65910d0f052498c5fa000444 000765 000024 00000001511 11652270306 022314 0ustar00zakirstaff000000 000000 x}Mo0 we;Elzb/bm-IrS׏!o^=m|0,W2b->N c[#kEO4g/F စGPءU,\mn>`p=4!:!*wB*}˚DgduPx7.jgŗ>DN{kЏ1h +\# X,YcCΖ[Od dc\qlEWF&-)`K?_l'zؽ[}Fa1;~b:Ys7r6Dicif4ؒ@Sr ݝ=r0N^Uy]ZP3‡o_X|*-!"=& 7/;\cx) ^ڙMpw Ŀ^Yrw¸ݮ+UU5*IeqUTOJąGcyX^cXv/=Zz`.%avf\,דZX,_ !pyad/.git/objects/8e/ef03b5500a56b09966aadb846092182982bc37000444 000765 000024 00000003016 11652270312 022013 0ustar00zakirstaff000000 000000 xWmo68&e7o5%_EamD9R/fLw=LNqN)w;>NS"!eIPQ)UK#LMj˄J$6Y^~#m6)5+K>KeGÖ]74 >Fң]Y*RVdO%aT~v: *m+ON.M,ql-\d<92R+W #ϩ%"DYgԲt#''Pyi|rg;x< Z5g6}(F2#FBkb+Ϊ4:RHew"!ZR`_ k=YThT+ J| IafTP̓ӇFfz+,;l/[F_y@ǹgv^62- ]$\b ؈nJ: Ku [R򁾾|w·uy;񛈮1fK(ll눋lcؕP^W8qtӈF ԢYpF$;% l~[k*h1a/w&m( 2$-PJ$@+v^z3ԨwMu/!&U'? Xr{TfK F9٫쯽ce)\$9 7Q3<0 7Te2:pyad/.git/objects/8a/d5ca1232bc2fedd9451c84362fffb18486c022000444 000765 000024 00000000137 11652270306 022305 0ustar00zakirstaff000000 000000 x5; E,EXJBQ|P)}\3Pj[8$ar!ۛME[ٖBjX3Q<]Zb‡'pyad/.git/objects/86/738360efdf4d74398aab00b2d73e115eb5f96d000444 000765 000024 00000001070 11652312132 022235 0ustar00zakirstaff000000 000000 xTMo@0HIRHudT!PVZU] D~}g5af|7f+߿hA '|eA=AqOrJ1@@&E]lp?IH4Q]mjm&Mŀ㣫w֡V}E;?Nh!lU Y 㟊`G.;+U v7 j:Κfq2Ʒ+ Eg%S%E!Q=l+$8,$fB/N]5jsGT\/`aWİlOs_P$aqcnZiZE<-IHz_% 6|`>;GY7M>ZR|YBlMm$4WDoN΋b^B-#]/LŠ"BBݮ[A٨&ި&Gv/6]6)\ Eb06ZgBU@<2OEƭ(t:]pHOpyad/.git/objects/7f/45f51b7b2956ad4b7640b38b24c34770a22629000444 000765 000024 00000013734 11652270306 021743 0ustar00zakirstaff000000 000000 x\ksz?l!P/mOubɮd<.@1 0hMM?`f#~qi.N:h9"[\e(r*JܨhT*x[)KD>?FN.^9,:͛gѫ_pk#@6[d*ojX7FHΐoh~ZcU=ST >zaKS2kLX{>?~PH=yf2}<6課f[ Haz0G[W:2!N∌Ȼ"#fpU{I,6shGQ%\Iۈ?3`Fum@L߸p5뵈PxBAOB o*Ia8򌧇>o˸^Z7gƫxWmCH@84d9yJ뼄yll'qCau 2cXӃz,S"Q4ңsE6M(k#8UK& &qG0#+n;GLUK-cMYV(=Nk:=LΏV9"T 9{&=ڔ ` ZumP ͳ$@ZuȀ Ye,mƭyO=P%2yG:^Xpd P^H+ZϚd, Fb '+&rɲDgt/jTh~ `W  %ic dtGpiʐ-D5mF%Ev'03FsV{~RABD5JУ=xYpi?/v*+&bR(u${6E`vL?z$ppr )03!0foمUk`,^dJ(*Y;u;`Uʲ[vVv}Dz.;yu2>Y7go.^_F߽>ݧLpX/JvG^jE\[71~;WXEp.HmВ-*HktQѿ_Y8,K7aHj9S*=,%@ bb_ jReklS1CI=^Lbp"RǦE ɈJ-W);J%TvL=aJ)mY.i5G9V.#p7|%F|'ʽmirvV!%¿V;/IqGQ$Q@9ږ֞(,5N&3>$akWZ S딺-Iei }exў%=IqFVҾ*%vJW^iG}隦U$3-CчG`˙Z;"*XoU/*;G/2'3oSJ+A״_o$`u媟jA dh'z/JCqt}, S,޲ }{YFq[]+fy w5Bb''^nPC2 %g(“\܃ۊaeswoDɅe!(>y 3Ӑ,>=ZiCD cHi]"MJܶ(#~0<&e_Gt^_"qzxd >`k| SZwssI,YYsT'8O$:jMG>~ Nt MK0H9 ^4p+K-£=JO-y2[m0ᜯb&qM.;kmM:jIHRl0V5QD8cWlI ӽ!:ǘb,tJ$u:9e-gzMNob7tt͢Hs6L[#XsUpNd"[2ș߉ŧt&#o7t!@CXr{<û83mq, ̼^ef2y@dN=&'4 Wܥ ['0s&T)~"Kz En.vlKè!yN˯*] pGCܡnqiy2rN4h qn[16*N>_C*l@`x0ӣ9{ C$ +rv@Ke" q\_V?g&#{AގnEMFZr!J+|V\fWoq#ԁK^52y]P=[t q'ض g=iVsC1=S|7A1HS<9CmNCs"0|0ǎp]Xƣc9j, <cOmӈmB"ztR?% Hq1AOCn=Z4%r:̨i!*BU,i?e6:&'4 R/ /+>>4D;(:[k_[s,4¶ #? mD/N_ ~! <3[yD-{mU" j,/tpyad/.git/objects/79/a585c9824f343643e71c672813b514e65a8b4e000444 000765 000024 00000000504 11652270334 021676 0ustar00zakirstaff000000 000000 xuK0}.{Y8Da]uʚvso!m3t&ft̼q7wEf|um%PLJ* 7\uʚ8͈udUFw$5FK0FM|J*Loصܯ k>Kh}Yqdy-=Ja3`eyL@RAGEhX\-q F|p{5cR.Pz4śHWnjj˹+D늴T4G9LoE3ۢpyad/.git/objects/74/ea41de2fab59541f24216f43718ce9d1a4483b000444 000765 000024 00000001341 11652270306 022150 0ustar00zakirstaff000000 000000 xUn0ـX.i{(Xv{H CФKRqޥDEi/Չ"3B9MՊP_]W-e{^.1d^(=+ó~à Y%YEs{e)r%%IȆssA8fMm Q:K1AnNq)AVONFMz3-E lں? 9/~ %O0xޢ3䥅̅l+j"B|& 7ˇ%K>"%hȐ@RFnא*+cO|$] xA5/c=xI!ڰR،r7y u-Pf n&-91d:}%3  ~T\-È86~ " =i5X+Hذc2,KYM]ӇND߿Lv]E&jVO~G<Sv.][]XlG` 5R&6rw P R@HHPUn!y=4"l^ {z)ehݔC LlA0W +b7^xQkaD %]IֽKd ЦDHzc2!gG,ZW\쫼c uyvKJ:,ToB@۲\(!ʺDL_a!댟 qpyad/.git/objects/72/62ede041b4af60f42762500e4f7065ddd45683000444 000765 000024 00000001721 11652270306 022010 0ustar00zakirstaff000000 000000 xWo6oA ;vȒIEQD d!޺}d)n[ޯ=FɯOQ*~#bqY|ZnVJѭ]]7ĽQlV6ZX-tLHnT&h*%`%3bx+x5g RIVJiS1MYes(GRW@:<νArV=Qeu[ӱAߓ 2LJN &!KhGBI?$s\.)c"_Q|[,>=Z#I#󪧥k`PL<ԯw d8sNkz7\t6_nkz샏c]~$(a rlC&#A=OǛ#eٟQ( 1Nx;^|c#~GCm ͟mɨOe4wruoz6o&;}o(V7 &0]>`߹G :pyad/.git/objects/67/fa492a8f74191520b880f87affcfd988344c8f000444 000765 000024 00000000065 11652270334 022220 0ustar00zakirstaff000000 000000 xKOR05eH+U(I-.)OLIJ,NU-/*QB+,M-Krpyad/.git/objects/65/19729cc9d7a497fff7c4513c11ea67a264c098000444 000765 000024 00000003016 11652314627 022126 0ustar00zakirstaff000000 000000 xWn8W? ,$onn5MlPAKV3d5@ș33g0DoY挎) >W""eIP^(Q#LE*dDNSn^EJDIo>Б6Jf%o^ҵ2أa.M3HD6ibKsXJ]IJ&{G;S JeIw Ǟ6жFJzJa%UPd)Nr$TJu/Y$NJRKz=Nf҈MBW,6R%ݲEn5 .Ř{C{i,~E렱$!f6s> (+Bƺs>%Db#RfB+UFRaHƄlq;zӇ?[&BxHzf~of;F~;[>,t8)=M~:qq-NO2"F4>):gǿ:ͼ>fϟ }ʲ SԏkgOB 6܇i22Y@fjD|DX#L 'WXCD?viΐ\tFae;r(;} 4#@m&C:˸ƺWt/B:ςD8BA+fXa6:ųW6@u}(¡0A͠Q_{$|7u;`=5,D,ѥo&T< ?);w}KCct0<35@gc.gcW@rr++_3z41Zx{kh`@*W¿@Q|@M]BxSo]a|1Jt4`9d&jn8⢯v|vvxo5;ŒF6P2Z;:[#nDGx}JX :߭kAxzi}3M_NPKFf:!X[_DIHλmXAkfl]O!Mw2 AUhNⓤPE֞AMKGv>Q7V%DL'F \rcE8T=g;;?@9,e#\Gfd7`~#\/|ӇA?<>pyad/.git/objects/5c/059c047dcdbfe124eae11bf4c84dddf7d2c14d000444 000765 000024 00000000262 11652270354 022662 0ustar00zakirstaff000000 000000 x+)JMU047c040031Qrutue{ϼ5+l̴U[&Ih6171^擯uTp>\%j@qjIi^A%߻;'b%Z(TTMIjqI|Fb^JNjHhwya qloԘ] S?pw|bF;5bFpyad/.git/objects/46/0bcb1413f46cb7b4c499bb821c4b2c7943f1cb000444 000765 000024 00000000253 11652312132 022270 0ustar00zakirstaff000000 000000 xMn 9\ HUEN?ZlMN >K[kuD1DlLX).86I:@\Ɏ5謙ș $LtyU8;MzM|[Z״/Ф=5Qz~^=Qtv6ɭӦ&7Y6s}jnrwsr . N&G5ouM5* U |r,M9NfDa'vqe~cXBjVαܞ! B@ ЏXmX#"0Fiis%\B*CfC؏4|&djY^ iփl ?wDM&4"NYv'm|'Sw_gw{s{7r;w|TW(M7L;ADس3;!؎tpyad/.git/objects/2b/c350a588e773ebb2bbfaa065c85c7e460a9482000444 000765 000024 00000003655 11652270306 022320 0ustar00zakirstaff000000 000000 xX[oF } CjM HdBr2Zw"QAvXfm;gx_{zytvV5^Ie*O>2|KJezM>!|zjNG99eI݂2T~dەԎCrXwWWw~y>$&ÜaM!%R3 L6RY*rS<\<٘N4ޡvy"kݖZ#fl⯑u89†H024dUF)\DUGh[ٌ*I d&/l"qg\̴2jQ-rWfȧRC[ȁ=u]蛯Ț=#P' `9upjs=0C1VZ-JuGfxH+Heҵ޺<,4dCKZG^! ,.cHee!aױJ◷VALv!J٣ᨯ>uihg_h+?p5٤XCr6> cҾ 1iU, eԏwm^d(!,6diXNEtǚo $ Щ%mHRa 4.fo-n mO0)2-BADp5Πn{w{|iLYU- '\t+k9lݪ.8l}يg Wsl: vbHW=>T(s0ϒ~VEC7%`CBaDQvDPt];ǎI0#%U'y8wtGEWWLQȪdi^j"09HI w)>m۾KrVBw#FV%zRs2txxzrrrv|}GeY_1.!tpLֽܒg )= Yິ* Jw"2_$ ʛlϖȱdB_0w}[ և  kUA`x0 ;J }4毠tx8!evHl,n[~cy-OחB#zp..jBə˽>p2  \^XHmp\#~4FhId1>wVp#q{06(T.̎1 @X# ߤL\?btNZ1M/ zd!5rZȾD0}V0eSǪ? yYyʭ@.x CKi׃ 2_h0J &rBE jo1;rPNI/S2$>cpyad/.git/objects/25/c507e69f55aba21544dc39ec18c37291f4e45c000444 000765 000024 00000000255 11652315003 022153 0ustar00zakirstaff000000 000000 xKj0@))!tSd7ƉHd mN_ ݼŃa3PdftBJ;l],; /sN>1dV5;t,>>;X#_ukt)S&R%,^=l414c`9%kraRX}G ?[KVhωM]3(KHn/mSpyad/.git/objects/15/fbff7fdbe31497917ad0452adb94c5c05e33ee000444 000765 000024 00000000263 11652312000 022442 0ustar00zakirstaff000000 000000 x+)JMU047c040031Qrutue{ϼ5+l̴U[&Ih6171^擯uTp>\%j@qjIi^A%ë}I1,Q8){*ļ"ߨ1ѻ bHe13xxŪ}kFpyad/.git/logs/HEAD000644 000765 000024 00000002300 11652315003 014516 0ustar00zakirstaff000000 000000 0000000000000000000000000000000000000000 9da3ad500d9542b9c292b09f573771b91abe00f9 Zakir Durumeric 1319727340 -0400 commit (initial): inital commit 9da3ad500d9542b9c292b09f573771b91abe00f9 e8fd8710ab8642f4e30b7d7f7418ed3533c0391d Zakir Durumeric 1319736320 -0400 commit: updating setup.py for correct upload to pypi e8fd8710ab8642f4e30b7d7f7418ed3533c0391d 460bcb1413f46cb7b4c499bb821c4b2c7943f1cb Zakir Durumeric 1319736410 -0400 commit: updating description 460bcb1413f46cb7b4c499bb821c4b2c7943f1cb 22e0f909b135732128fc69a5dd7ba00bbd326c6e Zakir Durumeric 1319736665 -0400 commit: moving readme for gitbhub compatibility. 22e0f909b135732128fc69a5dd7ba00bbd326c6e 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 Zakir Durumeric 1319736683 -0400 commit: moving readme for gitbhub compatibility. 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 308fb96832187b014259b2e5159eb0dbd16da76e Zakir Durumeric 1319737751 -0400 commit: correcting readme syntax 308fb96832187b014259b2e5159eb0dbd16da76e 25c507e69f55aba21544dc39ec18c37291f4e45c Zakir Durumeric 1319737859 -0400 commit: correcting readme syntax pyad/.git/logs/refs/000755 000765 000024 00000000000 11652270370 015045 5ustar00zakirstaff000000 000000 pyad/.git/logs/refs/heads/000755 000765 000024 00000000000 11652270354 016133 5ustar00zakirstaff000000 000000 pyad/.git/logs/refs/remotes/000755 000765 000024 00000000000 11652270370 016523 5ustar00zakirstaff000000 000000 pyad/.git/logs/refs/remotes/origin/000755 000765 000024 00000000000 11652270370 020012 5ustar00zakirstaff000000 000000 pyad/.git/logs/refs/remotes/origin/master000644 000765 000024 00000001351 11652315010 021217 0ustar00zakirstaff000000 000000 0000000000000000000000000000000000000000 9da3ad500d9542b9c292b09f573771b91abe00f9 Zakir Durumeric 1319727352 -0400 update by push 9da3ad500d9542b9c292b09f573771b91abe00f9 e8fd8710ab8642f4e30b7d7f7418ed3533c0391d Zakir Durumeric 1319736323 -0400 update by push e8fd8710ab8642f4e30b7d7f7418ed3533c0391d 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 Zakir Durumeric 1319736687 -0400 update by push 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 308fb96832187b014259b2e5159eb0dbd16da76e Zakir Durumeric 1319737754 -0400 update by push 308fb96832187b014259b2e5159eb0dbd16da76e 25c507e69f55aba21544dc39ec18c37291f4e45c Zakir Durumeric 1319737864 -0400 update by push pyad/.git/logs/refs/heads/master000644 000765 000024 00000002300 11652315003 017333 0ustar00zakirstaff000000 000000 0000000000000000000000000000000000000000 9da3ad500d9542b9c292b09f573771b91abe00f9 Zakir Durumeric 1319727340 -0400 commit (initial): inital commit 9da3ad500d9542b9c292b09f573771b91abe00f9 e8fd8710ab8642f4e30b7d7f7418ed3533c0391d Zakir Durumeric 1319736320 -0400 commit: updating setup.py for correct upload to pypi e8fd8710ab8642f4e30b7d7f7418ed3533c0391d 460bcb1413f46cb7b4c499bb821c4b2c7943f1cb Zakir Durumeric 1319736410 -0400 commit: updating description 460bcb1413f46cb7b4c499bb821c4b2c7943f1cb 22e0f909b135732128fc69a5dd7ba00bbd326c6e Zakir Durumeric 1319736665 -0400 commit: moving readme for gitbhub compatibility. 22e0f909b135732128fc69a5dd7ba00bbd326c6e 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 Zakir Durumeric 1319736683 -0400 commit: moving readme for gitbhub compatibility. 3b48706ad4ba9a8f19b4a6edb849f22e328d77a9 308fb96832187b014259b2e5159eb0dbd16da76e Zakir Durumeric 1319737751 -0400 commit: correcting readme syntax 308fb96832187b014259b2e5159eb0dbd16da76e 25c507e69f55aba21544dc39ec18c37291f4e45c Zakir Durumeric 1319737859 -0400 commit: correcting readme syntax pyad/.git/info/exclude000644 000765 000024 00000000360 11652270270 015447 0ustar00zakirstaff000000 000000 # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ pyad/.git/hooks/applypatch-msg.sample000755 000765 000024 00000000704 11652270270 020424 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : pyad/.git/hooks/commit-msg.sample000755 000765 000024 00000001600 11652270270 017543 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } pyad/.git/hooks/post-commit.sample000755 000765 000024 00000000240 11652270270 017741 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script that is called after a successful # commit is made. # # To enable this hook, rename this file to "post-commit". : Nothing pyad/.git/hooks/post-receive.sample000755 000765 000024 00000001050 11652270270 020073 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script for the "post-receive" event. # # The "post-receive" script is run after receive-pack has accepted a pack # and the repository has been updated. It is passed arguments in through # stdin in the form # # For example: # aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master # # see contrib/hooks/ for a sample, or uncomment the next line and # rename the file to "post-receive". #. /usr/share/doc/git-core/contrib/hooks/post-receive-email pyad/.git/hooks/post-update.sample000755 000765 000024 00000000275 11652270270 017743 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info pyad/.git/hooks/pre-applypatch.sample000755 000765 000024 00000000616 11652270270 020426 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : pyad/.git/hooks/pre-commit.sample000755 000765 000024 00000003052 11652270270 017546 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ascii filenames set this variable to true. allownonascii=$(git config hooks.allownonascii) # Cross platform projects tend to avoid non-ascii filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test "$(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0')" then echo "Error: Attempt to add a non-ascii file name." echo echo "This can cause problems if you want to work" echo "with people on other platforms." echo echo "To be portable it is advisable to rename the file ..." echo echo "If you know what you are doing you can disable this" echo "check using:" echo echo " git config hooks.allownonascii true" echo exit 1 fi exec git diff-index --check --cached $against -- pyad/.git/hooks/pre-rebase.sample000755 000765 000024 00000011527 11652270270 017525 0ustar00zakirstaff000000 000000 #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi exit 0 ################################################################ This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". pyad/.git/hooks/prepare-commit-msg.sample000755 000765 000024 00000002327 11652270270 021206 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" pyad/.git/hooks/update.sample000755 000765 000024 00000007033 11652270270 016757 0ustar00zakirstaff000000 000000 #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "Usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0