Advertisement
Guest User

samdbtest.py

a guest
Nov 25th, 2013
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 74.68 KB | None | 0 0
  1. # -\*- coding: UTF-8 -\*-
  2. #!/usr/bin/python
  3. #############################################################################
  4. # Name:         samdbtest.py
  5. # Purpose:      Example routines to access SamDB via Python2
  6. # Maintainers:  Torsten Kurbad <[email protected]>
  7. # Copyright:    (c) iwm-kmrc.de KMRC - Knowledge Media Research Center
  8. # License:      GPLv2
  9. #############################################################################
  10. __docformat__ = 'restructuredtext'
  11.  
  12. from base64 import b64decode, b64encode
  13. from time import time
  14.  
  15. import ldb
  16.  
  17. from samba import dsdb, unix2nttime
  18. from samba.auth import system_session
  19. from samba.common import normalise_int32
  20. from samba.credentials import Credentials
  21. from samba.dsdb import _samdb_get_domain_sid
  22. from samba.ndr import ndr_unpack, ndr_pack
  23. from samba.param import LoadParm
  24. from samba.samdb import SamDB
  25.  
  26. from samba.dsdb import (
  27.     GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
  28.     GTYPE_SECURITY_GLOBAL_GROUP,
  29.     GTYPE_SECURITY_UNIVERSAL_GROUP,
  30.     GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
  31.     GTYPE_DISTRIBUTION_GLOBAL_GROUP,
  32.     GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
  33.     UF_ACCOUNTDISABLE,
  34.     UF_DONT_EXPIRE_PASSWD,
  35.     UF_PASSWORD_EXPIRED,
  36.     UF_PASSWD_NOTREQD,
  37. )
  38.  
  39.  
  40. class ActiveDirectory(object):
  41.  
  42.     COUNTRYCODES=dict(
  43.         AF=('Afghanistan', '004'),
  44.         AX=('Åland-Inseln', '248'),
  45.         AL=('Albanien', '008'),
  46.         DZ=('Algerien', '012'),
  47.         AS=('Samoa', '016'),
  48.         AD=('Andorra', '020'),
  49.         AO=('Angola', '024'),
  50.         AI=('Anguilla', '660'),
  51.         AQ=('Antarktis', '010'),
  52.         AG=('Antigua und Barbuda', '028'),
  53.         AR=('Argentinien', '032'),
  54.         AM=('Armenien', '051'),
  55.         AW=('Aruba', '533'),
  56.         AU=('Australien', '036'),
  57.         AT=('Österreich', '040'),
  58.         AZ=('Aserbaidschan', '031'),
  59.         BS=('Bahamas', '044'),
  60.         BH=('Bahrain', '048'),
  61.         BD=('Bangladesch', '050'),
  62.         BB=('Barbados', '052'),
  63.         BY=('Weißrussland', '112'),
  64.         BE=('Belgien', '056'),
  65.         BZ=('Belize', '084'),
  66.         BJ=('Benin', '204'),
  67.         BM=('Bermuda', '060'),
  68.         BT=('Bhutan', '064'),
  69.         BO=('Bolivien, Plurinationaler Staat', '068'),
  70.         BQ=('Bonaire, Sint Eustatius und Saba', '535'),
  71.         BA=('Bosnien und Herzegowina', '070'),
  72.         BW=('Botsuana', '072'),
  73.         BV=('Bouvet-Insel', '074'),
  74.         BR=('Brasilien', '076'),
  75.         IO=('Britisches Territorium im Indischen Ozean', '086'),
  76.         BN=('Brunei Darussalam', '096'),
  77.         BG=('Bulgarien', '100'),
  78.         BF=('Burkina Faso', '854'),
  79.         BI=('Burundi', '108'),
  80.         KH=('Kambodscha', '116'),
  81.         CM=('Kamerun', '120'),
  82.         CA=('Kanada', '124'),
  83.         CV=('Kap Verde', '132'),
  84.         KY=('Cayman-Inseln', '136'),
  85.         CF=('Zentralafrikanische Republik', '140'),
  86.         TD=('Tschad', '148'),
  87.         CL=('Chile', '152'),
  88.         CN=('China', '156'),
  89.         CX=('Weihnachtsinseln', '162'),
  90.         CC=('Kokos-(Keeling-)Inseln', '166'),
  91.         CO=('Kolumbien', '170'),
  92.         KM=('Komoren', '174'),
  93.         CG=('Kongo', '178'),
  94.         CD=('Demokratische Republik Kongo', '180'),
  95.         CK=('Cookinseln', '184'),
  96.         CR=('Costa Rica', '188'),
  97.         CI=('Côte d\'Ivoire', '384'),
  98.         HR=('Kroatien', '191'),
  99.         CU=('Kuba', '192'),
  100.         CW=('Curaçao', '531'),
  101.         CY=('Zypern', '196'),
  102.         CZ=('Tschechische Republik', '203'),
  103.         DK=('Dänemark', '208'),
  104.         DJ=('Dschibuti', '262'),
  105.         DM=('Dominica', '212'),
  106.         DO=('Dominikanische Republik', '214'),
  107.         EC=('Ecuador', '218'),
  108.         EG=('Ägypten', '818'),
  109.         SV=('El Salvador', '222'),
  110.         GQ=('Äquatorialguinea', '226'),
  111.         ER=('Eritrea', '232'),
  112.         EE=('Estland', '233'),
  113.         ET=('Äthiopien', '231'),
  114.         FK=('Falklandinseln (Malwinen)', '238'),
  115.         FO=('Färöer-Inseln', '234'),
  116.         FJ=('Fidschi', '242'),
  117.         FI=('Finnland', '246'),
  118.         FR=('Frankreich', '250'),
  119.         GF=('Französisch-Guyana', '254'),
  120.         PF=('Französisch-Polynesien', '258'),
  121.         TF=('Französische Süd- und Antarktisgebiete', '260'),
  122.         GA=('Gabun', '266'),
  123.         GM=('Gambia', '270'),
  124.         GE=('Georgien', '268'),
  125.         DE=('Deutschland', '276'),
  126.         GH=('Ghana', '288'),
  127.         GI=('Gibraltar', '292'),
  128.         GR=('Griechenland', '300'),
  129.         GL=('Grönland', '304'),
  130.         GD=('Grenada', '308'),
  131.         GP=('Guadeloupe', '312'),
  132.         GU=('Guam', '316'),
  133.         GT=('Guatemala', '320'),
  134.         GG=('Guernsey', '831'),
  135.         GN=('Guinea', '324'),
  136.         GW=('Guinea-Bissau', '624'),
  137.         GY=('Guyana', '328'),
  138.         HT=('Haiti', '332'),
  139.         HM=('Heard und McDonaldinseln', '334'),
  140.         VA=('Heiliger Stuhl (Staat Vatikanstadt)', '336'),
  141.         HN=('Honduras', '340'),
  142.         HK=('Hongkong', '344'),
  143.         HU=('Ungarn', '348'),
  144.         IS=('Island', '352'),
  145.         IN=('Indien', '356'),
  146.         ID=('Indonesien', '360'),
  147.         IR=('Iran, Islamische Republik', '364'),
  148.         IQ=('Irak', '368'),
  149.         IE=('Irland', '372'),
  150.         IM=('Insel Man', '833'),
  151.         IL=('Israel', '376'),
  152.         IT=('Italien', '380'),
  153.         JM=('Jamaika', '388'),
  154.         JP=('Japan', '392'),
  155.         JE=('Jersey', '832'),
  156.         JO=('Jordanien', '400'),
  157.         KZ=('Kasachstan', '398'),
  158.         KE=('Kenia', '404'),
  159.         KI=('Kiribati', '296'),
  160.         KP=('Korea, Demokratische Volksrepublik', '408'),
  161.         KR=('Korea, Republik', '410'),
  162.         KW=('Kuwait', '414'),
  163.         KG=('Kirgisistan', '417'),
  164.         LA=('Laos, Demokratische Volksrepublik', '418'),
  165.         LV=('Lettland', '428'),
  166.         LB=('Libanon', '422'),
  167.         LS=('Lesotho', '426'),
  168.         LR=('Liberia', '430'),
  169.         LY=('Libysch-Arabische Dschamahirija', '434'),
  170.         LI=('Liechtenstein', '438'),
  171.         LT=('Litauen', '440'),
  172.         LU=('Luxemburg', '442'),
  173.         MO=('Macao', '446'),
  174.         MK=('Mazedonien, ehemalige jugoslawische Republik', '807'),
  175.         MG=('Madagaskar', '450'),
  176.         MW=('Malawi', '454'),
  177.         MY=('Malaysia', '458'),
  178.         MV=('Malediven', '462'),
  179.         ML=('Mali', '466'),
  180.         MT=('Malta', '470'),
  181.         MH=('Marshallinseln', '584'),
  182.         MQ=('Martinique', '474'),
  183.         MR=('Mauretanien', '478'),
  184.         MU=('Mauritius', '480'),
  185.         YT=('Mayotte', '175'),
  186.         MX=('Mexiko', '484'),
  187.         FM=('Mikronesien, Föderierte Staaten von', '583'),
  188.         MD=('Moldau, Republik', '498'),
  189.         MC=('Monaco', '492'),
  190.         MN=('Mongolei', '496'),
  191.         ME=('Montenegro', '499'),
  192.         MS=('Montserrat', '500'),
  193.         MA=('Marokko', '504'),
  194.         MZ=('Mosambik', '508'),
  195.         MM=('Myanmar', '104'),
  196.         NA=('Namibia', '516'),
  197.         NR=('Nauru', '520'),
  198.         NP=('Nepal', '524'),
  199.         NL=('Niederlande', '528'),
  200.         NC=('Neukaledonien', '540'),
  201.         NZ=('Neuseeland', '554'),
  202.         NI=('Nicaragua', '558'),
  203.         NE=('Niger', '562'),
  204.         NG=('Nigeria', '566'),
  205.         NU=('Niue', '570'),
  206.         NF=('Norfolkinsel', '574'),
  207.         MP=('Nördliche Mariana-Inseln', '580'),
  208.         NO=('Norwegen', '578'),
  209.         OM=('Oman', '512'),
  210.         PK=('Pakistan', '586'),
  211.         PW=('Palau', '585'),
  212.         PS=('Palästinensische Autonomiegebiete', '275'),
  213.         PA=('Panama', '591'),
  214.         PG=('Papua-Neuguinea', '598'),
  215.         PY=('Paraguay', '600'),
  216.         PE=('Peru', '604'),
  217.         PH=('Philippinen', '608'),
  218.         PN=('Pitcairn', '612'),
  219.         PL=('Polen', '616'),
  220.         PT=('Portugal', '620'),
  221.         PR=('Puerto Rico', '630'),
  222.         QA=('Katar', '634'),
  223.         RE=('Réunion', '638'),
  224.         RO=('Rumänien', '642'),
  225.         RU=('Russische Föderation', '643'),
  226.         RW=('Ruanda', '646'),
  227.         BL=('Saint-Barthélemy', '652'),
  228.         SH=('St. Helena, Ascension und Tristan da Cunha', '654'),
  229.         KN=('St. Kitts und Nevis', '659'),
  230.         LC=('St. Lucia', '662'),
  231.         MF=('Saint Martin (Französischer Teil)', '663'),
  232.         PM=('St. Pierre und Miquelon', '666'),
  233.         VC=('St. Vincent und die Grenadinen', '670'),
  234.         WS=('Samoa', '882'),
  235.         SM=('San Marino', '674'),
  236.         ST=('São Tomé und Príncipe', '678'),
  237.         SA=('Saudi-Arabien', '682'),
  238.         SN=('Senegal', '686'),
  239.         RS=('Serbien', '688'),
  240.         SC=('Seychellen', '690'),
  241.         SL=('Sierra Leone', '694'),
  242.         SG=('Singapur', '702'),
  243.         SX=('Saint-Martin', '702'),
  244.         SK=('Slowakei', '703'),
  245.         SI=('Slowenien', '705'),
  246.         SB=('Salomoninseln', '090'),
  247.         SO=('Somalia', '706'),
  248.         ZA=('Südafrika', '710'),
  249.         GS=('South Georgia und die Südlichen Sandwichinseln', '239'),
  250.         ES=('Spanien', '724'),
  251.         LK=('Sri Lanka', '144'),
  252.         SD=('Sudan', '736'),
  253.         SR=('Suriname', '740'),
  254.         SJ=('Svalbard und Jan Mayen', '744'),
  255.         SZ=('Swasiland', '748'),
  256.         SE=('Schweden', '752'),
  257.         CH=('Schweiz', '756'),
  258.         SY=('Syrien, Arabische Republik', '760'),
  259.         TW=('Taiwan, Chinesische Provinz', '158'),
  260.         TJ=('Tadschikistan', '762'),
  261.         TZ=('Tansania, Vereinigte Republik', '834'),
  262.         TH=('Thailand', '764'),
  263.         TL=('Timor-Leste', '626'),
  264.         TG=('Togo', '768'),
  265.         TK=('Tokelau', '772'),
  266.         TO=('Tonga', '776'),
  267.         TT=('Trinidad und Tobago', '780'),
  268.         TN=('Tunesien', '788'),
  269.         TR=('Türkei', '792'),
  270.         TM=('Turkmenistan', '795'),
  271.         TC=('Turks- und Caicosinseln', '796'),
  272.         TV=('Tuvalu', '798'),
  273.         UG=('Uganda', '800'),
  274.         UA=('Ukraine', '804'),
  275.         AE=('Vereinigte Arabische Emirate', '784'),
  276.         GB=('Vereinigtes Königreich', '826'),
  277.         US=('Vereinigte Staaten', '840'),
  278.         UM=('United States Minor Outlying Islands', '581'),
  279.         UY=('Uruguay', '858'),
  280.         UZ=('Usbekistan', '860'),
  281.         VU=('Vanuatu', '548'),
  282.         VE=('Venezuela, Bolivarische Republik', '862'),
  283.         VN=('Vietnam', '704'),
  284.         VG=('Britische Jungferninseln', '092'),
  285.         VI=('Amerikanische Jungferninseln', '850'),
  286.         WF=('Wallis und Futuna', '876'),
  287.         EH=('Westsahara', '732'),
  288.         YE=('Jemen', '887'),
  289.         ZM=('Sambia', '894'),
  290.         ZW=('Simbabwe', '716'),
  291.         )
  292.  
  293.     GROUPTYPES = dict(
  294.         SECURITY = dict(
  295.             DOMAIN = normalise_int32(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
  296.             GLOBAL = normalise_int32(GTYPE_SECURITY_GLOBAL_GROUP),
  297.             UNIVERSAL = normalise_int32(GTYPE_SECURITY_UNIVERSAL_GROUP)),
  298.         DISTRIBUTION = dict(
  299.             DOMAIN = normalise_int32(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
  300.             GLOBAL = normalise_int32(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
  301.             UNIVERSAL = normalise_int32(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP))
  302.         )
  303.  
  304.     ATTRMAP = dict(
  305.         facsimiletelephonenumber = 'fax',
  306.         l = 'location',
  307.         )
  308.  
  309.     def __init__(self, realm=None, url=None):
  310.         self.realm = realm
  311.         self.url = url
  312.         self._credentials = Credentials()
  313.         self._lp = LoadParm()
  314.         self._session_info = system_session()
  315.         self._connection = None
  316.         self._attrs = None
  317.  
  318.     def _connect(self, url=None):
  319.         if url is None:
  320.             if self.url is None:
  321.                 raise Exception('Need an URL to connect to.')
  322.             url = self.url
  323.         try:
  324.             connection = SamDB(url=url,
  325.                                session_info=self._session_info,
  326.                                credentials=self._credentials,
  327.                                lp=self._lp)
  328.         except Exception, e:
  329.             raise Exception('Unable to connect to SamDB. Reason: %s' % e)
  330.         self._connection = connection
  331.         self.url = url
  332.         return connection
  333.  
  334.     def _getDomainDN(self, connection=None):
  335.         if connection is None:
  336.             connection = self._connection
  337.         try:
  338.             return connection.domain_dn()
  339.         except Exception, e:
  340.             raise Exception('Unable to retrieve Domain DN. Reason: %s' % e)
  341.  
  342.     def _getDomainSID(self, connection=None):
  343.         if connection is None:
  344.             connection = self._connection
  345.         try:
  346.             return _samdb_get_domain_sid(self._connection)
  347.         except Exception, e:
  348.             raise Exception('Unable to retrieve Domain SID. Reason: %s' % e)
  349.  
  350.     def _search(self, expression=None, attrs=None, scope=ldb.SCOPE_SUBTREE,
  351.                 url=None, username=None, realm=None, password=None,
  352.                 lpOptions=None):
  353.         if self._connection is None:
  354.             self.connect(url=url, username=username, realm=realm,
  355.                          password=password, lpOptions=lpOptions)
  356.  
  357.         domainDN = self._getDomainDN()
  358.         try:
  359.             return self._connection.search(domainDN, scope=scope,
  360.                                            expression=expression,
  361.                                            attrs=attrs)
  362.         except Exception, e:
  363.             raise Exception('Unable to search Active Directory. Reason: %s'
  364.                                 % e)
  365.  
  366.     def _parseLDBMessage(self, message, utf8=False, attrMap=None):
  367.         if attrMap is None:
  368.             attrMap = self.ATTRMAP
  369.         dn = message.get('dn').extended_str()
  370.         attrs = {}
  371.         for (key, element) in message.items():
  372.             if key != 'dn':
  373.                 attr = []
  374.                 for value in element:
  375.                     if utf8:
  376.                         try:
  377.                             value = value.decode('utf-8')
  378.                         except UnicodeDecodeError:
  379.                             pass
  380.                     attr.append(value)
  381.                 attrs[key.lower()] = attr
  382.                 if key.lower() in attrMap.keys():
  383.                     attrs[attrMap[key.lower()].lower()] = attr
  384.         return (dn, attrs)
  385.  
  386.     def _parseLDBMessageList(self, messageList, utf8=False):
  387.         result = []
  388.         for message in messageList:
  389.             result.append(self._parseLDBMessage(message, utf8=utf8))
  390.         return result
  391.  
  392.     def _modifyFromDict(self, modifyDN, modifyDict,
  393.                         attrs=None,
  394.                         url=None, username=None, realm=None, password=None,
  395.                         lpOptions=None):
  396.         if self._connection is None:
  397.             self.connect(url=url, username=username, realm=realm,
  398.                          password=password, lpOptions=lpOptions)
  399.  
  400.         expression = '(dn=%s)' % modifyDN
  401.         ldbEntry = self._search(expression=expression, attrs=attrs)
  402.         try:
  403.             assert(len(ldbEntry) == 1)
  404.         except AssertionError:
  405.             raise Exception('Unable to find entry for DN "%s"' % dn)
  406.  
  407.         (ldbEntryDN, ldbEntryDict) = self._parseLDBMessage(ldbEntry[0])
  408.  
  409.         message = ldb.Message()
  410.         message.dn = ldb.Dn(self._connection, modifyDN)
  411.    
  412.         for (modifyKey, modifyValue) in modifyDict.items():
  413.             ldbFlag = ldb.FLAG_MOD_ADD
  414.             if modifyKey.lower() in ldbEntryDict.keys():
  415.                 ldbFlag = ldb.FLAG_MOD_REPLACE
  416.                 if modifyValue in (None, '', []):
  417.                     ldbFlag = ldb.FLAG_MOD_DELETE
  418.                     modifyValue = ldbEntryDict[modifyKey.lower()]
  419.  
  420.             if (modifyValue is not None) and (modifyValue != ''):
  421.                 message[modifyKey] = ldb.MessageElement(modifyValue,
  422.                                                         ldbFlag,
  423.                                                         modifyKey)
  424.         if len(message.keys()) > 1:
  425.             self._connection.modify(message)
  426.  
  427.     def _setLpOptions(self, lpOptions=None):
  428.         if lpOptions is not None:
  429.             for (key, value) in lpOptions.items():
  430.                 if (key == 'realm') and (self.realm is None):
  431.                     self.realm = value
  432.                 self._lp.set(key, value)
  433.  
  434.     def _setCredentials(self, username, password, realm=None):
  435.         if realm is None:
  436.             realm = self.realm
  437.         self._credentials.set_realm(realm)
  438.         self._credentials.set_username(username)
  439.         self._credentials.set_password(password)
  440.  
  441.     def getOUs(self, attrs=[],
  442.               url=None, username=None, realm=None, password=None,
  443.               lpOptions=None):
  444.         if self._connection is None:
  445.             self.connect(url=url, username=username, realm=realm,
  446.                          password=password, lpOptions=lpOptions)
  447.         expression = '(objectClass=organizationalUnit)'
  448.         result = self._search(expression=expression, attrs=attrs)
  449.  
  450.         return self._parseLDBMessageList(result)
  451.  
  452.     def getOU(self, organizationalUnitRDN, attrs=[],
  453.               url=None, username=None, realm=None, password=None,
  454.               lpOptions=None):
  455.         if self._connection is None:
  456.             self.connect(url=url, username=username, realm=realm,
  457.                          password=password, lpOptions=lpOptions)
  458.  
  459.         domainDN = self._getDomainDN()
  460.  
  461.         expression = ('(&(objectClass=organizationalUnit)(dn=%s,%s))' %
  462.                       (organizationalUnitRDN, domainDN))
  463.         result = self._search(expression=expression, attrs=attrs)
  464.  
  465.         return self._parseLDBMessageList(result)
  466.  
  467.     def existsOU(self, organizationalUnitRDN,
  468.                  url=None, username=None, realm=None, password=None,
  469.                  lpOptions=None):
  470.         result = self.getOU(organizationalUnitRDN, attrs=None,
  471.                             url=url, username=username, realm=realm,
  472.                             password=password, lpOptions=lpOptions)
  473.         return (result is not None) and (len(result) > 0)
  474.  
  475.     def createOU(self, organizationalUnitRDN,
  476.                  description=None, name=None, sd=None,
  477.                  url=None, username=None, realm=None, password=None,
  478.                  lpOptions=None):
  479.         if self._connection is None:
  480.             self.connect(url=url, username=username, realm=realm,
  481.                          password=password, lpOptions=lpOptions)
  482.  
  483.         domainDN = self._getDomainDN()
  484.  
  485.         if not self.existsOU(organizationalUnitRDN):
  486.             message = dict(
  487.                 dn = '%s,%s' % (organizationalUnitRDN, domainDN),
  488.                 objectClass = 'organizationalUnit',
  489.                 ou = organizationalUnitRDN.split('=')[1].strip(),
  490.             )
  491.  
  492.             if description is not None:
  493.                 message['description'] = description
  494.             if name is not None:
  495.                 message['name'] = name
  496.             if sd is not None:
  497.                 message['nTSecurityDescriptor'] = ndr_pack(sd)
  498.             self._connection.add(message)
  499.  
  500.     def deleteOU(self, organizationalUnitRDN,
  501.                  url=None, username=None, realm=None, password=None,
  502.                  lpOptions=None):
  503.         if self._connection is None:
  504.             self.connect(url=url, username=username, realm=realm,
  505.                          password=password, lpOptions=lpOptions)
  506.  
  507.         domainDN = self._getDomainDN()
  508.  
  509.         expression = ('(&(objectClass=organizationalUnit)(dn=%s,%s))' %
  510.                       (organizationalUnitRDN, domainDN))
  511.         organizationalUnit = self._search(expression=expression, attrs=[])
  512.  
  513.         try:
  514.             assert(len(organizationalUnit) == 1)
  515.         except AssertionError:
  516.             raise Exception('Unable to find Organizational Unit %s.'
  517.                             % organizationalUnitRDN)
  518.  
  519.         self._connection.delete(organizationalUnit[0].dn)
  520.  
  521.     def setMinPwdAge(self, minPwdAge,
  522.                      url=None, username=None, realm=None, password=None,
  523.                      lpOptions=None):
  524.         if self._connection is None:
  525.             self.connect(url=url, username=username, realm=realm,
  526.                          password=password, lpOptions=lpOptions)
  527.  
  528.         if not isinstance(minPwdAge, basestring):
  529.             minPwdAge = '%d' % minPwdAge
  530.         message = ldb.Message()
  531.         message.dn = ldb.Dn(self._connection, self._getDomainDN())
  532.         message['minPwdAge'] = ldb.MessageElement(minPwdAge,
  533.                                                   ldb.FLAG_MOD_REPLACE,
  534.                                                   'minPwdAge')
  535.         self._connection.modify(message)
  536.  
  537.     def getMinPwdAge(self,
  538.                      url=None, username=None, realm=None, password=None,
  539.                      lpOptions=None):
  540.         minPwdAge = self._search(attrs=['minPwdAge'],
  541.                                  scope=ldb.SCOPE_BASE,
  542.                                  url=url, username=username, realm=realm,
  543.                                  password=password, lpOptions=lpOptions)
  544.         if len(minPwdAge) == 0:
  545.             return None
  546.         if not 'minPwdAge' in minPwdAge[0]:
  547.             return None
  548.         return minPwdAge[0]['minPwdAge'][0]
  549.  
  550.     def setMaxPwdAge(self, maxPwdAge,
  551.                      url=None, username=None, realm=None, password=None,
  552.                      lpOptions=None):
  553.         if self._connection is None:
  554.             self.connect(url=url, username=username, realm=realm,
  555.                          password=password, lpOptions=lpOptions)
  556.  
  557.         if not isinstance(maxPwdAge, basestring):
  558.             maxPwdAge = '%d' % maxPwdAge
  559.         message = ldb.Message()
  560.         message.dn = ldb.Dn(self._connection, self._getDomainDN())
  561.         message['maxPwdAge'] = ldb.MessageElement(maxPwdAge,
  562.                                                   ldb.FLAG_MOD_REPLACE,
  563.                                                   'maxPwdAge')
  564.         self._connection.modify(message)
  565.  
  566.     def getMaxPwdAge(self,
  567.                      url=None, username=None, realm=None, password=None,
  568.                      lpOptions=None):
  569.         maxPwdAge = self._search(attrs=['maxPwdAge'],
  570.                                  scope=ldb.SCOPE_BASE,
  571.                                  url=url, username=username, realm=realm,
  572.                                  password=password, lpOptions=lpOptions)
  573.         if len(maxPwdAge) == 0:
  574.             return None
  575.         if not 'maxPwdAge' in maxPwdAge[0]:
  576.             return None
  577.         return maxPwdAge[0]['maxPwdAge'][0]
  578.  
  579.     def setMinPwdLength(self, minPwdLength,
  580.                         url=None, username=None, realm=None, password=None,
  581.                         lpOptions=None):
  582.         if self._connection is None:
  583.             self.connect(url=url, username=username, realm=realm,
  584.                          password=password, lpOptions=lpOptions)
  585.  
  586.         if not isinstance(minPwdLength, basestring):
  587.             minPwdLength = '%d' % minPwdLength
  588.         message = ldb.Message()
  589.         message.dn = ldb.Dn(self._connection, self._getDomainDN())
  590.         message['minPwdLength'] = ldb.MessageElement(minPwdLength,
  591.                                                      ldb.FLAG_MOD_REPLACE,
  592.                                                      'minPwdLength')
  593.         self._connection.modify(message)
  594.  
  595.     def getMinPwdLength(self,
  596.                         url=None, username=None, realm=None, password=None,
  597.                         lpOptions=None):
  598.         minPwdLength = self._search(attrs=['minPwdLength'],
  599.                                     scope=ldb.SCOPE_BASE,
  600.                                     url=url, username=username, realm=realm,
  601.                                     password=password, lpOptions=lpOptions)
  602.         if len(minPwdLength) == 0:
  603.             return None
  604.         if not 'minPwdLength' in minPwdLength[0]:
  605.             return None
  606.         return minPwdLength[0]['minPwdLength'][0]
  607.  
  608.     def setPwdHistoryLength(self, pwdHistoryLength,
  609.                             url=None, username=None, realm=None, password=None,
  610.                             lpOptions=None):
  611.         if self._connection is None:
  612.             self.connect(url=url, username=username, realm=realm,
  613.                          password=password, lpOptions=lpOptions)
  614.  
  615.         if not isinstance(pwdHistoryLength, basestring):
  616.             pwdHistoryLength = '%d' % pwdHistoryLength
  617.         message = ldb.Message()
  618.         message.dn = ldb.Dn(self._connection, self._getDomainDN())
  619.         message['pwdHistoryLength'] = ldb.MessageElement(pwdHistoryLength,
  620.                                                          ldb.FLAG_MOD_REPLACE,
  621.                                                          'pwdHistoryLength')
  622.         self._connection.modify(message)
  623.  
  624.     def getPwdHistoryLength(self,
  625.                             url=None, username=None, realm=None, password=None,
  626.                             lpOptions=None):
  627.         pwdHistoryLength = self._search(attrs=['pwdHistoryLength'],
  628.                                         scope=ldb.SCOPE_BASE,
  629.                                         url=url, username=username, realm=realm,
  630.                                         password=password, lpOptions=lpOptions)
  631.         if len(pwdHistoryLength) == 0:
  632.             return None
  633.         if not 'pwdHistoryLength' in pwdHistoryLength[0]:
  634.             return None
  635.         return pwdHistoryLength[0]['pwdHistoryLength'][0]
  636.  
  637.     def setPwdProperties(self, pwdProperties,
  638.                          url=None, username=None, realm=None, password=None,
  639.                          lpOptions=None):
  640.         if self._connection is None:
  641.             self.connect(url=url, username=username, realm=realm,
  642.                          password=password, lpOptions=lpOptions)
  643.  
  644.         if not isinstance(pwdProperties, basestring):
  645.             pwdProperties = '%d' % pwdProperties
  646.         message = ldb.Message()
  647.         message.dn = ldb.Dn(self._connection, self._getDomainDN())
  648.         message['pwdProperties'] = ldb.MessageElement(pwdProperties,
  649.                                                       ldb.FLAG_MOD_REPLACE,
  650.                                                       'pwdProperties')
  651.         self._connection.modify(message)
  652.  
  653.     def getPwdProperties(self,
  654.                          url=None, username=None, realm=None, password=None,
  655.                          lpOptions=None):
  656.         pwdProperties = self._search(attrs=['pwdProperties'],
  657.                                      scope=ldb.SCOPE_BASE,
  658.                                      url=url, username=username, realm=realm,
  659.                                      password=password, lpOptions=lpOptions)
  660.         if len(pwdProperties) == 0:
  661.             return None
  662.         if not 'pwdProperties' in pwdProperties[0]:
  663.             return None
  664.         return pwdProperties[0]['pwdProperties'][0]
  665.  
  666.     def connect(self, url=None, username=None, realm=None, password=None,
  667.                 lpOptions=None):
  668.         if lpOptions is not None:
  669.             self._setLpOptions(lpOptions)
  670.         if realm is None:
  671.             realm = self.realm
  672.         if username is None and self._credentials.get_username() is None:
  673.             raise Exception(
  674.                 'Cannot modify Active Directory without admin credentials.')
  675.         if username is not None and self._credentials.get_username() is None:
  676.             self._setCredentials(username, password, realm=realm)
  677.         if self._connection is None:
  678.             return self._connect(url)
  679.  
  680.  
  681. class ADUser(ActiveDirectory):
  682.  
  683.     def __init__(self, realm=None, url=None):
  684.         super(ADUser, self).__init__(realm=realm, url=url)
  685.  
  686.         self._orgAttrs = [
  687.             'c',
  688.             'cn',
  689.             'co',
  690.             'company',
  691.             'department',
  692.             'description',
  693.             'displayName',
  694.             'facsimileTelephoneNumber',
  695.             'givenName',
  696.             'homePhone',
  697.             'initials',
  698.             'l',
  699.             'mail',
  700.             'manager',
  701.             'mobile',
  702.             'name',
  703.             'personalTitle',
  704.             'physicalDeliveryOfficeName',
  705.             'postalCode',
  706.             'secretary',
  707.             'seeAlso',
  708.             'serialNumber',
  709.             'sn',
  710.             'streetAddress',
  711.             'thumbnailPhoto',
  712.             'telephoneNumber',
  713.             'title',
  714.             ]
  715.         self._sambaAttrs = [
  716.             'accountExpires',
  717.             'badPasswordTime',
  718.             'badPwdCount',
  719.             'codePage',
  720.             'countryCode',
  721.             'distinguishedName',
  722.             'homeDirectory',
  723.             'homeDrive',
  724.             'instanceType',
  725.             'lastLogoff',
  726.             'lastLogon',
  727.             'logonCount',
  728.             'memberOf',
  729.             'objectCategory',
  730.             'objectClass',
  731.             'objectGUID',
  732.             'objectSID',
  733.             'primaryGroupID',
  734.             'profilePath',
  735.             'pwdLastSet',
  736.             'sAMAccountName',
  737.             'sAMAccountType',
  738.             'userAccountControl',
  739.             'userPrincipalName',
  740.             'uSNChanged',
  741.             'uSNCreated',
  742.             'whenChanged',
  743.             'whenCreated',
  744.             ]
  745.         self._unixAttrs = [
  746.             'gidNumber',
  747.             'loginShell',
  748.             'uidNumber',
  749.             'unixHomeDirectory',
  750.             ]
  751.         self._attrs=self._orgAttrs + self._sambaAttrs + self._unixAttrs
  752.  
  753.     def _search(self, expression=None, attrs=None, scope=ldb.SCOPE_SUBTREE,
  754.                 url=None, username=None, realm=None, password=None,
  755.                 lpOptions=None):
  756.         if self._connection is None:
  757.             self.connect(url=url, username=username, realm=realm,
  758.                          password=password, lpOptions=lpOptions)
  759.  
  760.         if (not attrs) and (not self.url.startswith('ldap')):
  761.             attrs = self._attrs
  762.  
  763.         return super(ADUser, self)._search(expression=expression,
  764.                                            attrs=attrs,
  765.                                            scope=scope,
  766.                                            url=url,
  767.                                            username=username,
  768.                                            realm=realm,
  769.                                            password=password,
  770.                                            lpOptions=lpOptions)
  771.  
  772.     def _toggleADUserAccountFlags(self, sAMAccountName, flags, setFlags=True,
  773.                                   url=None, username=None, realm=None,
  774.                                   password=None, lpOptions=None):
  775.         """toggle_userAccountFlags
  776.  
  777.        """
  778.         if self._connection is None:
  779.             self.connect(url=url, username=username, realm=realm,
  780.                          password=password, lpOptions=lpOptions)
  781.         domainDN = self._getDomainDN()
  782.  
  783.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  784.                       % (ldb.binary_encode(sAMAccountName),
  785.                          ldb.binary_encode(sAMAccountName),
  786.                          'CN=Person,CN=Schema,CN=Configuration',
  787.                          domainDN))
  788.         adUser = self._search(expression=expression,
  789.                               attrs=['userAccountControl'])
  790.         try:
  791.             assert(len(adUser) == 1)
  792.         except AssertionError:
  793.             raise Exception('Unable to find user "%s"' % sAMAccountName)
  794.  
  795.         adUserDN = adUser[0].dn
  796.  
  797.         oldUAC = int(adUser[0]['userAccountControl'][0])
  798.         newUAC = oldUAC & ~flags
  799.         if setFlags:
  800.             newUAC = oldUAC | flags
  801.  
  802.         if oldUAC == newUAC:
  803.             return
  804.  
  805.         userAccountFlags = """
  806. dn: %s
  807. changetype: modify
  808. delete: userAccountControl
  809. userAccountControl: %u
  810. add: userAccountControl
  811. userAccountControl: %u
  812. """ % (adUserDN, oldUAC, newUAC)
  813.         self._connection.modify_ldif(userAccountFlags)
  814.  
  815.     def getADUsers(self, attrs=[], utf8=False,
  816.                   url=None, username=None, realm=None, password=None,
  817.                   lpOptions=None):
  818.         expression = ('(&(objectClass=user)(userAccountControl:%s:=%u))'
  819.                       % (ldb.OID_COMPARATOR_AND, dsdb.UF_NORMAL_ACCOUNT))
  820.         result = self._search(expression=expression, attrs=attrs,
  821.                               url=url, username=username, realm=realm,
  822.                               password=password, lpOptions=lpOptions)
  823.  
  824.         return self._parseLDBMessageList(result, utf8=utf8)
  825.  
  826.     def getADUser(self, sAMAccountName, attrs=[], utf8=False,
  827.                   url=None, username=None, realm=None, password=None,
  828.                   lpOptions=None):
  829.         if self._connection is None:
  830.             self.connect(url=url, username=username, realm=realm,
  831.                          password=password, lpOptions=lpOptions)
  832.  
  833.         domainDN = self._getDomainDN()
  834.  
  835.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  836.                       % (ldb.binary_encode(sAMAccountName),
  837.                          ldb.binary_encode(sAMAccountName),
  838.                          'CN=Person,CN=Schema,CN=Configuration',
  839.                          domainDN))
  840.         result = self._search(expression=expression, attrs=attrs,
  841.                               url=url, username=username, realm=realm,
  842.                               password=password, lpOptions=lpOptions)
  843.  
  844.         return self._parseLDBMessageList(result, utf8=utf8)
  845.  
  846.     def existsADUser(self, sAMAccountName,
  847.                      url=None, username=None, realm=None, password=None,
  848.                      lpOptions=None):
  849.  
  850.         result = self.getADUser(sAMAccountName, realm=realm, url=url,
  851.                                 username=username, password=password,
  852.                                 lpOptions=lpOptions)
  853.         return (result is not None) and (len(result) > 0)
  854.  
  855.     def createADUser(self,
  856.                      sAMAccountName, userPassword,
  857.                      surName=None, givenName=None, initials=None,
  858.                      personalTitle=None, description=None,
  859.                      title=None, department=None, company=None,
  860.                      serialNumber=None, manager=None, managerOU=None,
  861.                      secretary=None, secretaryOU=None,
  862.                      seeAlso=None, seeAlsoOU=None,
  863.                      physicalDeliveryOfficeName=None,
  864.                      streetAddress=None, postalCode=None, location=None,
  865.                      country=None,
  866.                      telephoneNumber=None, facsimileTelephoneNumber=None,
  867.                      homePhone=None, mobile=None,
  868.                      mail=None, wWWHomePage=None,
  869.                      jpegPhoto=None,
  870.                      profilePath=None, scriptPath=None, homeDrive=None,
  871.                      homeDirectory=None,
  872.                      uidNumber=None, gidNumber=None, loginShell=None,
  873.                      unixHomeDirectory=None,
  874.                      changePasswordAtNextLogin=True,
  875.                      useSAMAccountNameAsCN=True,
  876.                      userOU=None, sd=None, setPassword=True,
  877.                      expirySeconds=None,
  878.                      neverExpireAccount=True, neverExpirePassword=False,
  879.                      extraAttrs={},
  880.                      url=None, username=None, realm=None, password=None,
  881.                      lpOptions=None):
  882.  
  883.         if self.existsADUser(sAMAccountName, realm=realm, url=url,
  884.                              username=username, password=password,
  885.                              lpOptions=lpOptions):
  886.             raise Exception(
  887.                 'Cannot create user %s. A user with that name already exists.'
  888.                 % sAMAccountName)
  889.  
  890.         domainDN = self._getDomainDN()
  891.  
  892.         # Initial and Common Attributes
  893.         ldbMessage = dict(
  894.             objectClass = ['user',],
  895.             sAMAccountName = sAMAccountName,
  896.         )
  897.  
  898.         displayName = ''
  899.         if givenName is not None:
  900.             displayName += givenName
  901.             ldbMessage['givenName'] = givenName
  902.      
  903.         if initials is not None:
  904.             if not initials.endswith('.'):
  905.                 initials = '%s.' % initials
  906.             displayName += initials
  907.             ldbMessage['initials'] = initials
  908.  
  909.         if surName is not None:
  910.             displayName += ' %s' % surName
  911.             ldbMessage['sn'] = surName
  912.  
  913.         if displayName is not '':
  914.             ldbMessage['displayName'] = displayName
  915.             ldbMessage['name'] = displayName
  916.  
  917.         cn = sAMAccountName
  918.         if not useSAMAccountNameAsCN and (displayName is not ''):
  919.             cn = displayName
  920.  
  921.         for ou in [userOU, managerOU, secretaryOU, seeAlsoOU]:
  922.             if ou is not None:
  923.                 if not self.existsOU(ou):
  924.                     raise Exception('Unable to find Organizational Unit %s for %s.'
  925.                                     % (ou, ou.__name__))
  926.  
  927.         userDN = 'CN=%s,%s,%s' % (cn, (userOU or 'CN=Users'), domainDN)
  928.         ldbMessage['dn'] = userDN
  929.  
  930.         dnsDomain = ldb.Dn(
  931.             self._connection, domainDN).canonical_str().replace('/', '')
  932.         userPrincipalName = '%s@%s' % (sAMAccountName, dnsDomain)
  933.         ldbMessage['userPrincipalName'] = userPrincipalName
  934.  
  935.         # Standard Samba Account Information
  936.         if title is not None:
  937.             ldbMessage['title'] = title
  938.  
  939.         if department is not None:
  940.             ldbMessage['department'] = department
  941.        
  942.         if company is not None:
  943.             ldbMessage['company'] = company
  944.  
  945.         if manager is not None:
  946.             ldbMessage['manager'] = ('CN=%s,%s,%s'
  947.                 % (manager, (managerOU or 'CN=Users'), domainDN))
  948.  
  949.         if description is None:
  950.             description = displayName
  951.         ldbMessage['description'] = description
  952.  
  953.         if mail is not None:
  954.             ldbMessage['mail'] = mail
  955.  
  956.         if wWWHomePage is not None:
  957.             ldbMessage['wWWHomePage'] = wWWHomePage
  958.  
  959.         if streetAddress is not None:
  960.             ldbMessage['streetAddress'] = streetAddress
  961.  
  962.         if postalCode is not None:
  963.             ldbMessage['postalCode'] = '%s' % postalCode
  964.  
  965.         if location is not None:
  966.             ldbMessage['l'] = location
  967.            
  968.         if country is not None:
  969.             if len(country) == 2:
  970.                 if country.upper() in self.COUNTRYCODES.keys():
  971.                     ldbMessage['c'] = country.upper()
  972.                     ldbMessage['co'] = self.COUNTRYCODES[country.upper()][0]
  973.                     ldbMessage['countryCode'] = self.COUNTRYCODES[country.upper()][1]
  974.             else:
  975.                 for (countryCode, (countryName, countryNumeric)) in self.COUNTRYCODES:
  976.                     if country.lower() == countryName.lower():
  977.                         ldbMessage['c'] = countryCode
  978.                         ldbMessage['co'] = countryName
  979.                         ldbMessage['countryCode'] = countryNumeric
  980.                         continue
  981.    
  982.         if physicalDeliveryOfficeName is not None:
  983.             ldbMessage['physicalDeliveryOfficeName'] = physicalDeliveryOfficeName
  984.  
  985.         if telephoneNumber is not None:
  986.             ldbMessage['telephoneNumber'] = telephoneNumber
  987.  
  988.         if facsimileTelephoneNumber is not None:
  989.             ldbMessage['facsimileTelephoneNumber'] = facsimileTelephoneNumber
  990.  
  991.         if homePhone is not None:
  992.             ldbMessage['homePhone'] = homePhone
  993.  
  994.         if mobile is not None:
  995.             ldbMessage['mobile'] = mobile
  996.  
  997.         if profilePath is not None:
  998.             ldbMessage['profilePath'] = profilePath
  999.            
  1000.         if scriptPath is not None:
  1001.             ldbMessage['scriptPath'] = scriptPath
  1002.  
  1003.         if homeDrive is not None:
  1004.             if not homeDrive.endswith(':'):
  1005.                 homeDrive = '%s:' % homeDrive
  1006.             ldbMessage['homeDrive'] = homeDrive.upper()
  1007.  
  1008.         if homeDirectory is not None:
  1009.             ldbMessage['homeDirectory'] = homeDirectory
  1010.  
  1011.         if sd is not None:
  1012.             ldbMessage['nTSecurityDescriptor'] = ndr_pack(sd)
  1013.  
  1014.         # Additional Organizational Information
  1015.         if personalTitle is not None:
  1016.             ldbMessage['personalTitle'] = personalTitle
  1017.  
  1018.         if serialNumber is not None:
  1019.             ldbMessage['serialNumber'] = '%s' % serialNumber
  1020.  
  1021.         if secretary is not None:
  1022.             ldbMessage['secretary'] = ('CN=%s,%s,%s'
  1023.                 % (secretary, (secretaryOU or 'CN=Users'), domainDN))
  1024.  
  1025.         if seeAlso is not None:
  1026.             ldbMessage['seeAlso'] = ('CN=%s,%s,%s'
  1027.                 % (seeAlso, (seeAlsoOU or 'CN=Users'), domainDN))
  1028.  
  1029.         if jpegPhoto is not None:
  1030.             # jpegPhoto must be either the contents of a file with correct
  1031.             #  JPEG header (ff 08 hex), ...
  1032.             if jpegPhoto.startswith('\xff\xd8'):
  1033.                 jpegPhoto = b64encode(jpegPhoto)
  1034.             try:
  1035.                 # ... or a base64 encoded string representation of such file
  1036.                 #  contents
  1037.                 jpegPhotoContents = b64decode(jpegPhoto)
  1038.                 assert(jpegPhotoContents[:2] == '\xff\xd8')
  1039.             except:
  1040.                 pass
  1041.             else:
  1042.                 ldbMessage['jpegPhoto'] = jpegPhoto
  1043.  
  1044.         # Posix Attributes
  1045.         if uidNumber is not None:
  1046.             ldbMessage['objectClass'].extend(['posixAccount', 'shadowAccount'])
  1047.             ldbMessage['uidNumber'] = '%s' % uidNumber
  1048.  
  1049.         if gidNumber is not None:
  1050.             ldbMessage['gidNumber'] = '%s' % gidNumber
  1051.  
  1052.         if loginShell is not None:
  1053.             ldbMessage['loginShell'] = loginShell
  1054.  
  1055.         if unixHomeDirectory is not None:
  1056.             ldbMessage['unixHomeDirectory'] = unixHomeDirectory
  1057.  
  1058.         # Additional Attributes (must be properly formatted)
  1059.         for (extraKey, extraValue) in extraAttrs.items():
  1060.             ldbMessage[extraKey] = extraValue
  1061.  
  1062.         # Create new User
  1063.         self._connection.transaction_start()
  1064.  
  1065.         try:
  1066.             self._connection.add(ldbMessage)
  1067.  
  1068.             # Set the password
  1069.             if setPassword:
  1070.                 self.setADUserPassword(
  1071.                     sAMAccountName,
  1072.                     userPassword,
  1073.                     changePasswordAtNextLogin=changePasswordAtNextLogin)
  1074.             self.setADUserExpiry(sAMAccountName,
  1075.                                  expirySeconds=expirySeconds,
  1076.                                  neverExpireAccount=neverExpireAccount,
  1077.                                  neverExpirePassword=neverExpirePassword)
  1078.         except:
  1079.             self._connection.transaction_cancel()
  1080.             raise
  1081.         else:
  1082.             self._connection.transaction_commit()
  1083.  
  1084.     def modifyADUser(self,
  1085.                      sAMAccountName,
  1086.                      surName=None, givenName=None, initials=None,
  1087.                      personalTitle=None, description=None, displayName=None,
  1088.                      title=None, department=None, company=None,
  1089.                      serialNumber=None, manager=None, managerOU=None,
  1090.                      secretary=None, secretaryOU=None,
  1091.                      seeAlso=None, seeAlsoOU=None,
  1092.                      physicalDeliveryOfficeName=None,
  1093.                      streetAddress=None, postalCode=None, location=None,
  1094.                      country=None,
  1095.                      telephoneNumber=None, facsimileTelephoneNumber=None,
  1096.                      homePhone=None, mobile=None,
  1097.                      mail=None, wWWHomePage=None,
  1098.                      jpegPhoto=None,
  1099.                      profilePath=None, scriptPath=None, homeDrive=None,
  1100.                      homeDirectory=None,
  1101.                      uidNumber=None, gidNumber=None, loginShell=None,
  1102.                      unixHomeDirectory=None,
  1103.                      expirySeconds=None,
  1104.                      neverExpireAccount=None, neverExpirePassword=None,
  1105.                      extraAttrs={},
  1106.                      useSAMAccountNameAsCN=True,
  1107.                      userOU=None, sd=None,
  1108.                      url=None, username=None, realm=None, password=None,
  1109.                      lpOptions=None):
  1110.  
  1111.         if self._connection is None:
  1112.             self.connect(url=url, username=username, realm=realm,
  1113.                          password=password, lpOptions=lpOptions)
  1114.         domainDN = self._getDomainDN()
  1115.  
  1116.         attrs = []
  1117.         if not self.url.startswith('ldap'):
  1118.             attrs = self._attrs
  1119.             attrs.extend(extraAttrs.keys())
  1120.  
  1121.         cn = sAMAccountName
  1122.         if not useSAMAccountNameAsCN and (displayName is not None):
  1123.             cn = displayName
  1124.  
  1125.         for ouStr in ['userOU', 'managerOU', 'secretaryOU', 'seeAlsoOU']:
  1126.             ou = eval(ouStr)
  1127.             if ou is not None:
  1128.                 if not self.existsOU(ou):
  1129.                     raise Exception('Unable to find Organizational Unit %s for %s.'
  1130.                                     % (ou, ouStr))
  1131.  
  1132.         userDN = 'CN=%s,%s,%s' % (cn, (userOU or 'CN=Users'), domainDN)
  1133.  
  1134.         ldbMessage = {}
  1135.         # Standard Samba Account Information
  1136.         if surName is not None:
  1137.             ldbMessage['sn'] = surName
  1138.        
  1139.         if givenName is not None:
  1140.             ldbMessage['givenName'] = givenName
  1141.        
  1142.         if displayName is not None:
  1143.             ldbMessage['displayName'] = displayName
  1144.             ldbMessage['name'] = displayName
  1145.  
  1146.         if initials is not None:
  1147.             if not initials.endswith('.'):
  1148.                 initials = '%s.' % initials
  1149.             ldbMessage['initials'] = initials
  1150.  
  1151.         if title is not None:
  1152.             ldbMessage['title'] = title
  1153.        
  1154.         if department is not None:
  1155.             ldbMessage['department'] = department
  1156.        
  1157.         if company is not None:
  1158.             ldbMessage['company'] = company
  1159.  
  1160.         if manager is not None:
  1161.             ldbMessage['manager'] = ('CN=%s,%s,%s'
  1162.                 % (manager, (managerOU or 'CN=Users'), domainDN))
  1163.  
  1164.         if description is not None:
  1165.             ldbMessage['description'] = description
  1166.  
  1167.         if mail is not None:
  1168.             ldbMessage['mail'] = mail
  1169.  
  1170.         if wWWHomePage is not None:
  1171.             ldbMessage['wWWHomePage'] = wWWHomePage
  1172.  
  1173.         if streetAddress is not None:
  1174.             ldbMessage['streetAddress'] = streetAddress
  1175.  
  1176.         if postalCode is not None:
  1177.             ldbMessage['postalCode'] = '%s' % postalCode
  1178.  
  1179.         if location is not None:
  1180.             ldbMessage['l'] = location
  1181.  
  1182.         if country is not None:
  1183.             if len(country) == 2:
  1184.                 if country.upper() in self.COUNTRYCODES.keys():
  1185.                     ldbMessage['c'] = country.upper()
  1186.                     ldbMessage['co'] = self.COUNTRYCODES[country.upper()][0]
  1187.                     ldbMessage['countryCode'] = self.COUNTRYCODES[country.upper()][1]
  1188.             else:
  1189.                 for (countryCode, (countryName, countryNumeric)) in self.COUNTRYCODES:
  1190.                     if country.lower() == countryName.lower():
  1191.                         ldbMessage['c'] = countryCode
  1192.                         ldbMessage['co'] = countryName
  1193.                         ldbMessage['countryCode'] = countryNumeric
  1194.                         continue
  1195.  
  1196.         if physicalDeliveryOfficeName is not None:
  1197.             ldbMessage['physicalDeliveryOfficeName'] = physicalDeliveryOfficeName
  1198.  
  1199.         if telephoneNumber is not None:
  1200.             ldbMessage['telephoneNumber'] = telephoneNumber
  1201.  
  1202.         if facsimileTelephoneNumber is not None:
  1203.             ldbMessage['facsimileTelephoneNumber'] = facsimileTelephoneNumber
  1204.  
  1205.         if homePhone is not None:
  1206.             ldbMessage['homePhone'] = homePhone
  1207.  
  1208.         if mobile is not None:
  1209.             ldbMessage['mobile'] = mobile
  1210.  
  1211.         if profilePath is not None:
  1212.             ldbMessage['profilePath'] = profilePath
  1213.            
  1214.         if scriptPath is not None:
  1215.             ldbMessage['scriptPath'] = scriptPath
  1216.  
  1217.         if homeDrive is not None:
  1218.             if not homeDrive.endswith(':'):
  1219.                 homeDrive = '%s:' % homeDrive
  1220.             ldbMessage['homeDrive'] = homeDrive.upper()
  1221.  
  1222.         if homeDirectory is not None:
  1223.             ldbMessage['homeDirectory'] = homeDirectory
  1224.  
  1225.         if sd is not None:
  1226.             ldbMessage['nTSecurityDescriptor'] = ndr_pack(sd)
  1227.  
  1228.         # Additional Organizational Information
  1229.         if personalTitle is not None:
  1230.             ldbMessage['personalTitle'] = personalTitle
  1231.  
  1232.         if serialNumber is not None:
  1233.             ldbMessage['serialNumber'] = '%s' % serialNumber
  1234.  
  1235.         if secretary is not None:
  1236.             ldbMessage['secretary'] = ('CN=%s,%s,%s'
  1237.                 % (secretary, (secretaryOU or 'CN=Users'), domainDN))
  1238.  
  1239.         if seeAlso is not None:
  1240.             ldbMessage['seeAlso'] = ('CN=%s,%s,%s'
  1241.                 % (seeAlso, (seeAlsoOU or 'CN=Users'), domainDN))
  1242.  
  1243.         if jpegPhoto is not None:
  1244.             # jpegPhoto must be either the contents of a file with correct
  1245.             #  JPEG header (ff 08 hex), ...
  1246.             if jpegPhoto.startswith('\xff\xd8'):
  1247.                 jpegPhoto = b64encode(jpegPhoto)
  1248.             try:
  1249.                 # ... or a base64 encoded string representation of such file
  1250.                 #  contents
  1251.                 jpegPhotoContents = b64decode(jpegPhoto)
  1252.                 assert(jpegPhotoContents[:2] == '\xff\xd8')
  1253.             except:
  1254.                 pass
  1255.             else:
  1256.                 ldbMessage['jpegPhoto'] = jpegPhoto
  1257.  
  1258.         # Posix Attributes
  1259.         if uidNumber is not None:
  1260.             ldbMessage['objectClass'] = ['user',
  1261.                                          'posixAccount',
  1262.                                          'shadowAccount',]
  1263.             ldbMessage['uidNumber'] = '%s' % uidNumber
  1264.  
  1265.         if gidNumber is not None:
  1266.             ldbMessage['gidNumber'] = '%s' % gidNumber
  1267.  
  1268.         if loginShell is not None:
  1269.             ldbMessage['loginShell'] = loginShell
  1270.  
  1271.         if unixHomeDirectory is not None:
  1272.             ldbMessage['unixHomeDirectory'] = unixHomeDirectory
  1273.  
  1274.         # Additional Attributes
  1275.         for (extraKey, extraValue) in extraAttrs.items():
  1276.             ldbMessage[extraKey] = extraValue
  1277.  
  1278.         # Modify User
  1279.         self._connection.transaction_start()
  1280.         try:
  1281.             if ldbMessage is not {}:
  1282.                 self._modifyFromDict(userDN,
  1283.                                      ldbMessage,
  1284.                                      attrs=attrs)
  1285.             self.setADUserExpiry(sAMAccountName,
  1286.                                  expirySeconds=expirySeconds,
  1287.                                  neverExpireAccount=neverExpireAccount,
  1288.                                  neverExpirePassword=neverExpirePassword)
  1289.         except:
  1290.             self._connection.transaction_cancel()
  1291.             raise
  1292.         self._connection.transaction_commit()
  1293.  
  1294.     def renameADUser(self, sAMAccountName, newSAMAccountName, newUserOU=None,
  1295.                      url=None, username=None, realm=None, password=None,
  1296.                      lpOptions=None):
  1297.         if self._connection is None:
  1298.             self.connect(url=url, username=username, realm=realm,
  1299.                          password=password, lpOptions=lpOptions)
  1300.  
  1301.         attrs = []
  1302.         if not self.url.startswith('ldap'):
  1303.             attrs = self._attrs
  1304.  
  1305.         domainDN = self._getDomainDN()
  1306.         dnsDomain = ldb.Dn(
  1307.             self._connection, domainDN).canonical_str().replace('/', '')
  1308.  
  1309.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1310.                       % (ldb.binary_encode(sAMAccountName),
  1311.                          ldb.binary_encode(sAMAccountName),
  1312.                          'CN=Person,CN=Schema,CN=Configuration',
  1313.                          domainDN))
  1314.         adUser = self._search(expression=expression, attrs=attrs)
  1315.         try:
  1316.             assert(len(adUser) == 1)
  1317.         except AssertionError:
  1318.             raise Exception('Unable to find user %s.' % sAMAccountName)
  1319.  
  1320.         oldUserDN = adUser[0].dn
  1321.  
  1322.         if newUserOU is not None:
  1323.             if not self.existsOU(newUserOU):
  1324.                 raise Exception(
  1325.                     'Cannot add user %s to non existing Organizational Unit %s.'
  1326.                     % (sAMAccountName, newUserOU))
  1327.        
  1328.         newUserDN = 'CN=%s,%s,%s' % (newSAMAccountName,
  1329.                                      (newUserOU or 'CN=Users'),
  1330.                                      domainDN)
  1331.  
  1332.         newUserPrincipalName = '%s@%s' % (newSAMAccountName, dnsDomain)
  1333.  
  1334.         extraAttrs=dict(sAMAccountName = newSAMAccountName,
  1335.                         userPrincipalName = newUserPrincipalName)
  1336.  
  1337.         self._connection.transaction_start()
  1338.         try:
  1339.             self._connection.rename(oldUserDN, newUserDN)
  1340.             self.modifyADUser(newSAMAccountName,
  1341.                               extraAttrs=extraAttrs)
  1342.         except:
  1343.             self._connection.transaction_cancel()
  1344.             raise
  1345.         self._connection.transaction_commit()
  1346.  
  1347.     def deleteADUser(self, sAMAccountName,
  1348.                      url=None, username=None, realm=None, password=None,
  1349.                      lpOptions=None):
  1350.         """Deletes a user
  1351.  
  1352.        :param sAMAccountName: Name of the target user
  1353.        """
  1354.         if self._connection is None:
  1355.             self.connect(url=url, username=username, realm=realm,
  1356.                          password=password, lpOptions=lpOptions)
  1357.  
  1358.         domainDN = self._getDomainDN()
  1359.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1360.                       % (ldb.binary_encode(sAMAccountName),
  1361.                          ldb.binary_encode(sAMAccountName),
  1362.                          'CN=Person,CN=Schema,CN=Configuration',
  1363.                          domainDN))
  1364.  
  1365.         adUser = self._search(expression=expression, attrs=None)
  1366.  
  1367.         try:
  1368.             assert(len(adUser) == 1)
  1369.         except AssertionError:
  1370.             raise Exception('Unable to find user %s.' % sAMAccountName)
  1371.  
  1372.         self._connection.delete(adUser[0].dn)
  1373.  
  1374.     def setADUserPassword(self, sAMAccountName, userPassword,
  1375.                           changePasswordAtNextLogin=False,
  1376.                           url=None, username=None, realm=None, password=None,
  1377.                           lpOptions=None):
  1378.         """Sets the password for a user
  1379.  
  1380.        """
  1381.         if self._connection is None:
  1382.             self.connect(url=url, username=username, realm=realm,
  1383.                          password=password, lpOptions=lpOptions)
  1384.  
  1385.         domainDN = self._getDomainDN()
  1386.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1387.                          % (ldb.binary_encode(sAMAccountName),
  1388.                              ldb.binary_encode(sAMAccountName),
  1389.                              'CN=Person,CN=Schema,CN=Configuration',
  1390.                              domainDN))
  1391.         adUser = self._search(expression=expression, attrs=None)
  1392.  
  1393.         try:
  1394.             assert(len(adUser) == 1)
  1395.         except AssertionError:
  1396.             raise Exception('Unable to find user "%s"' % sAMAccountName)
  1397.  
  1398.         adUserDN = adUser[0].dn
  1399.         unicodePwd = """
  1400. dn: %s
  1401. changetype: modify
  1402. replace: unicodePwd
  1403. unicodePwd:: %s
  1404. """ % (adUserDN, b64encode(('"' + userPassword + '"').encode('utf-16-le')))
  1405.  
  1406.         self._connection.transaction_start()
  1407.         try:
  1408.             self._connection.modify_ldif(unicodePwd)
  1409.             if changePasswordAtNextLogin:
  1410.                 self.mustChangePasswordAtNextLogin(sAMAccountName)
  1411.             self.enableADUser(sAMAccountName)
  1412.         except:
  1413.             self._connection.transaction_cancel()
  1414.             raise
  1415.         self._connection.transaction_commit()
  1416.  
  1417.     def setADUserExpiry(self, sAMAccountName, expirySeconds=None,
  1418.                         neverExpireAccount=False, neverExpirePassword=False,
  1419.                         url=None, username=None, realm=None, password=None,
  1420.                         lpOptions=None):
  1421.         """Sets the account expiry for a user
  1422.  
  1423.        """
  1424.         if self._connection is None:
  1425.             self.connect(url=url, username=username, realm=realm,
  1426.                          password=password, lpOptions=lpOptions)
  1427.  
  1428.         domainDN = self._getDomainDN()
  1429.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1430.                          % (ldb.binary_encode(sAMAccountName),
  1431.                              ldb.binary_encode(sAMAccountName),
  1432.                              'CN=Person,CN=Schema,CN=Configuration',
  1433.                              domainDN))
  1434.  
  1435.         adUser = self._search(expression=expression,
  1436.                               attrs=['accountExpires'])
  1437.         try:
  1438.             assert(len(adUser) == 1)
  1439.         except AssertionError:
  1440.             raise Exception('Unable to find user "%s"' % sAMAccountName)
  1441.  
  1442.         adUserDN = adUser[0].dn
  1443.  
  1444.         oldAccountExpires = int(adUser[0]['accountExpires'][0])
  1445.         accountExpires = None
  1446.  
  1447.         if expirySeconds is not None:
  1448.             accountExpires = unix2nttime(expirySeconds + int(time()))
  1449.         if neverExpireAccount:
  1450.             accountExpires = 0
  1451.         if (accountExpires is None) and not neverExpireAccount:
  1452.             accountExpires = oldAccountExpires
  1453.  
  1454.         setADUserExpiry = """
  1455. dn: %s
  1456. changetype: modify
  1457. replace: accountExpires
  1458. accountExpires: %u
  1459. """ % (adUserDN, accountExpires)
  1460.  
  1461.         self._connection.transaction_start()
  1462.         try:
  1463.             self._toggleADUserAccountFlags(sAMAccountName,
  1464.                                            flags=UF_DONT_EXPIRE_PASSWD,
  1465.                                            setFlags=neverExpirePassword)
  1466.             self._connection.modify_ldif(setADUserExpiry)
  1467.         except:
  1468.             self._connection.transaction_cancel()
  1469.             raise
  1470.         self._connection.transaction_commit()
  1471.  
  1472.     def getADUserExpiry(self, sAMAccountName,
  1473.                         url=None, username=None, realm=None, password=None,
  1474.                         lpOptions=None):
  1475.         if self._connection is None:
  1476.             self.connect(url=url, username=username, realm=realm,
  1477.                          password=password, lpOptions=lpOptions)
  1478.  
  1479.         domainDN = self._getDomainDN()
  1480.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1481.                          % (ldb.binary_encode(sAMAccountName),
  1482.                              ldb.binary_encode(sAMAccountName),
  1483.                              'CN=Person,CN=Schema,CN=Configuration',
  1484.                              domainDN))
  1485.  
  1486.         adUser = self._search(expression=expression,
  1487.                               attrs=['accountExpires',
  1488.                                      'pwdLastSet',
  1489.                                      'userAccountControl'])
  1490.         try:
  1491.             assert(len(adUser) == 1)
  1492.         except AssertionError:
  1493.             raise Exception('Unable to find user "%s"' % sAMAccountName)
  1494.  
  1495.         (adUserDN, adUserDict) = self._parseLDBMessage(adUser[0])
  1496.  
  1497.         adUserExpiry = dict(
  1498.             accountExpires = adUserDict['accountexpires'][0],
  1499.             mustChangePasswordAtNextLogin = (int(adUserDict['pwdlastset'][0]) == 0),
  1500.             passwordNeverExpires = (
  1501.                 int(adUserDict['useraccountcontrol'][0])
  1502.                 & UF_DONT_EXPIRE_PASSWD == UF_DONT_EXPIRE_PASSWD),
  1503.             accountNeverExpires = (int(adUserDict['accountexpires'][0]) == 0))
  1504.  
  1505.         return (adUserDN, adUserExpiry)
  1506.  
  1507.     def disableADUser(self, sAMAccountName,
  1508.                      url=None, username=None, realm=None, password=None,
  1509.                      lpOptions=None):
  1510.         """Disables an account
  1511.  
  1512.        """
  1513.         flags = UF_ACCOUNTDISABLE
  1514.         self._toggleADUserAccountFlags(sAMAccountName, flags, setFlags=True,
  1515.                                        url=url, username=username, realm=realm,
  1516.                                        lpOptions=lpOptions)
  1517.  
  1518.     def enableADUser(self, sAMAccountName,
  1519.                      url=None, username=None, realm=None, password=None,
  1520.                      lpOptions=None):
  1521.         """Enables an account
  1522.  
  1523.        """
  1524.         flags = UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD
  1525.         self._toggleADUserAccountFlags(sAMAccountName, flags, setFlags=False,
  1526.                                        url=url, username=username, realm=realm,
  1527.                                        lpOptions=lpOptions)
  1528.  
  1529.     def mustChangePasswordAtNextLogin(self, sAMAccountName,
  1530.                      url=None, username=None, realm=None, password=None,
  1531.                      lpOptions=None):
  1532.         """Forces a password change at next login
  1533.  
  1534.        """
  1535.         if self._connection is None:
  1536.             self.connect(url=url, username=username, realm=realm,
  1537.                          password=password, lpOptions=lpOptions)
  1538.  
  1539.         domainDN = self._getDomainDN()
  1540.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1541.                       % (ldb.binary_encode(sAMAccountName),
  1542.                          ldb.binary_encode(sAMAccountName),
  1543.                          'CN=Person,CN=Schema,CN=Configuration',
  1544.                          domainDN))
  1545.         adUser = self._search(expression=expression, attrs=None)
  1546.  
  1547.         try:
  1548.             assert(len(adUser) == 1)
  1549.         except AssertionError:
  1550.             raise Exception('Unable to find user "%s"' % sAMAccountName)
  1551.  
  1552.         adUserDN = adUser[0].dn
  1553.         resetPwdLastSet = """
  1554. dn: %s
  1555. changetype: modify
  1556. replace: pwdLastSet
  1557. pwdLastSet: 0
  1558. """ % (adUserDN)
  1559.  
  1560.         self._connection.transaction_start()
  1561.         try:
  1562.             self._connection.modify_ldif(resetPwdLastSet)
  1563.             self._toggleADUserAccountFlags(sAMAccountName,
  1564.                                            UF_PASSWORD_EXPIRED)
  1565.         except:
  1566.             self._connection.transaction_cancel()
  1567.             raise
  1568.         self._connection.transaction_commit()
  1569.  
  1570.  
  1571. class ADGroup(ActiveDirectory):
  1572.  
  1573.     def __init__(self, realm=None, url=None):
  1574.         super(ADGroup, self).__init__(realm=realm, url=url)
  1575.  
  1576.         self._orgAttrs = [
  1577.             'cn',
  1578.             'description',
  1579.             'displayName',
  1580.             'name',
  1581.             ]
  1582.         self._sambaAttrs = [
  1583.             'distinguishedName',
  1584.             'groupType',
  1585.             'instanceType',
  1586.             'member',
  1587.             'objectCategory',
  1588.             'objectClass',
  1589.             'objectGUID',
  1590.             'objectSID',
  1591.             'sAMAccountName',
  1592.             'sAMAccountType',
  1593.             'uSNChanged',
  1594.             'uSNCreated',
  1595.             'whenChanged',
  1596.             'whenCreated',
  1597.             ]
  1598.         self._unixAttrs = [
  1599.             'gidNumber',
  1600.             ]
  1601.         self._attrs=self._orgAttrs + self._sambaAttrs + self._unixAttrs
  1602.  
  1603.     def _search(self, expression=None, attrs=None, scope=ldb.SCOPE_SUBTREE,
  1604.                 url=None, username=None, realm=None, password=None,
  1605.                 lpOptions=None):
  1606.         if self._connection is None:
  1607.             self.connect(url=url, username=username, realm=realm,
  1608.                          password=password, lpOptions=lpOptions)
  1609.  
  1610.         if (not attrs) and (not self.url.startswith('ldap')):
  1611.             attrs = self._attrs
  1612.  
  1613.         return super(ADGroup, self)._search(expression=expression,
  1614.                                            attrs=attrs,
  1615.                                            scope=scope,
  1616.                                            url=url,
  1617.                                            username=username,
  1618.                                            realm=realm,
  1619.                                            password=password,
  1620.                                            lpOptions=lpOptions)
  1621.  
  1622.     def _addRemoveADGroupMembers(self, cn, adUsersList,
  1623.         addOperation=True,
  1624.         url=None, username=None, realm=None, password=None,
  1625.         lpOptions=None):
  1626.  
  1627.         if self._connection is None:
  1628.             self.connect(url=url, username=username, realm=realm,
  1629.                          password=password, lpOptions=lpOptions)
  1630.  
  1631.         if isinstance(adUsersList, basestring):
  1632.             adUsersList = adUsersList.split(',')
  1633.  
  1634.         domainDN = self._getDomainDN()
  1635.  
  1636.         expression = '(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))' % (
  1637.             ldb.binary_encode(cn),
  1638.             ldb.binary_encode(cn),
  1639.             'CN=Group,CN=Schema,CN=Configuration',
  1640.             domainDN)
  1641.  
  1642.         adGroup = self._search(expression=expression, attrs=['member'])
  1643.         try:
  1644.             assert(len(adGroup) == 1)
  1645.         except AssertionError:
  1646.             raise Exception('Unable to find group %s.' % cn)
  1647.  
  1648.  
  1649.         modified = False
  1650.         addADUserToGroup = """
  1651. dn: %s
  1652. changetype: modify
  1653. """ % (str(adGroup[0].dn))
  1654.  
  1655.         for adUser in adUsersList:
  1656.             expression = '(|(sAMAccountName=%s)(CN=%s))' % (
  1657.                 ldb.binary_encode(adUser.strip()),
  1658.                 ldb.binary_encode(adUser.strip()))
  1659.             adGroupMember = self._search(expression=expression, attrs=None)
  1660.             if len(adGroupMember) != 1:
  1661.                 continue
  1662.  
  1663.             if (addOperation and
  1664.                 (adGroup[0].get('member') is None or
  1665.                 str(adGroupMember[0].dn) not in adGroup[0]['member'])):
  1666.                 modified = True
  1667.                 addADUserToGroup += """add: member
  1668. member: %s
  1669. """ % (str(adGroupMember[0].dn))
  1670.  
  1671.             elif (not addOperation and
  1672.                   (adGroup[0].get('member') is not None and
  1673.                    str(adGroupMember[0].dn) in adGroup[0]['member'])):
  1674.                 modified = True
  1675.                 addADUserToGroup += """delete: member
  1676. member: %s
  1677. """ % (str(adGroupMember[0].dn))
  1678.  
  1679.         if modified:
  1680.             self._connection.modify_ldif(addADUserToGroup)
  1681.        
  1682.     def getADGroups(self, attrs=[], utf8=False,
  1683.                     url=None, username=None, realm=None, password=None,
  1684.                     lpOptions=None):
  1685.         if self._connection is None:
  1686.             self.connect(url=url, username=username, realm=realm,
  1687.                          password=password, lpOptions=lpOptions)
  1688.         domainDN = self._getDomainDN()
  1689.  
  1690.         expression = ('(objectCategory=%s,%s)'
  1691.                       % ('CN=Group,CN=Schema,CN=Configuration', domainDN))
  1692.         result = self._search(expression=expression, attrs=attrs,
  1693.                               url=url, username=username, realm=realm,
  1694.                               password=password, lpOptions=lpOptions)
  1695.  
  1696.         return self._parseLDBMessageList(result, utf8=utf8)
  1697.  
  1698.     def getADGroup(self, cn, attrs=[], utf8=False,
  1699.                    url=None, username=None, realm=None, password=None,
  1700.                    lpOptions=None):
  1701.         if self._connection is None:
  1702.             self.connect(url=url, username=username, realm=realm,
  1703.                          password=password, lpOptions=lpOptions)
  1704.         domainDN = self._getDomainDN()
  1705.  
  1706.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1707.                       % (ldb.binary_encode(cn),
  1708.                          ldb.binary_encode(cn),
  1709.                          'CN=Group,CN=Schema,CN=Configuration',
  1710.                          domainDN))
  1711.         result = self._search(expression=expression, attrs=attrs,
  1712.                               url=url, username=username, realm=realm,
  1713.                               password=password, lpOptions=lpOptions)
  1714.  
  1715.         return self._parseLDBMessageList(result, utf8=utf8)
  1716.  
  1717.     def existsADGroup(self, cn, realm=None, url=None,
  1718.                       username=None, password=None, lpOptions=None):
  1719.         result = self.getADGroup(cn, realm=realm, url=url,
  1720.                                 username=username, password=password,
  1721.                                 lpOptions=lpOptions)
  1722.         return (result is not None) and (len(result) > 0)
  1723.  
  1724.     def createADGroup(self,
  1725.                       cn, displayName, description=None,
  1726.                       groupOU=None, groupType='SECURITY', groupScope='GLOBAL',
  1727.                       mail=None, info=None,
  1728.                       gidNumber=None,
  1729.                       useDisplayNameAsCN=False,
  1730.                       sd=None, extraAttrs={},
  1731.                       url=None, username=None, realm=None, password=None,
  1732.                       lpOptions=None):
  1733.         """Adds a new group with additional parameters
  1734.  
  1735.        """
  1736.         if self._connection is None:
  1737.             self.connect(url=url, username=username, realm=realm,
  1738.                          password=password, lpOptions=lpOptions)
  1739.  
  1740.         if useDisplayNameAsCN:
  1741.             cn = displayName
  1742.  
  1743.         domainDN = self._getDomainDN()
  1744.  
  1745.         if groupOU is not None:
  1746.             if not self.existsOU(groupOU):
  1747.                 raise Exception('Unable to find Organizational Unit %s for group %s.'
  1748.                                 % (ou, cn))
  1749.  
  1750.         groupDN = 'CN=%s,%s,%s' % (cn, (groupOU or 'CN=Users'), domainDN)
  1751.  
  1752.         # Standard Samba Account Information
  1753.         ldbMessage = {'dn': groupDN,
  1754.                       'sAMAccountName': cn,
  1755.                       'displayName': displayName,
  1756.                       'objectClass': ['group']}
  1757.  
  1758.         groupType = (groupType or 'SECURITY')
  1759.         groupScope = (groupScope or 'GLOBAL')
  1760.  
  1761.         ldbMessage['groupType'] = (
  1762.             self.GROUPTYPES[groupType.upper()][groupScope.upper()])
  1763.  
  1764.         if description is None:
  1765.             description = displayName
  1766.         ldbMessage['description'] = description
  1767.  
  1768.         if mail is not None:
  1769.             ldbMessage['mail'] = mail
  1770.  
  1771.         if info is not None:
  1772.             ldbMessage['info'] = info
  1773.  
  1774.         if sd is not None:
  1775.             ldbMessage['nTSecurityDescriptor'] = ndr_pack(sd)
  1776.  
  1777.         # Posix Attributes
  1778.         if gidNumber is not None:
  1779.             ldbMessage['gidNumber'] = '%s' % gidNumber
  1780.  
  1781.         # Additional Attributes (must be properly formatted)
  1782.         for (extraKey, extraValue) in extraAttrs.items():
  1783.             ldbMessage[extraKey] = extraValue
  1784.  
  1785.         self._connection.add(ldbMessage)
  1786.  
  1787.     def modifyADGroup(self,
  1788.                       cn, displayName=None, description=None,
  1789.                       groupOU=None, groupType=None, groupScope=None,
  1790.                       mail=None, info=None,
  1791.                       gidNumber=None,
  1792.                       sd=None, extraAttrs={},
  1793.                       url=None, username=None, realm=None, password=None,
  1794.                       lpOptions=None):
  1795.         if self._connection is None:
  1796.             self.connect(url=url, username=username, realm=realm,
  1797.                          password=password, lpOptions=lpOptions)
  1798.  
  1799.         attrs = []    
  1800.         if not self.url.startswith('ldap'):
  1801.             attrs = self._attrs
  1802.             attrs.extend(extraAttrs.keys())
  1803.  
  1804.         domainDN = self._getDomainDN()
  1805.  
  1806.         if groupOU is not None:
  1807.             if not self.existsOU(groupOU):
  1808.                 raise Exception('Unable to find Organizational Unit %s for group %s.'
  1809.                                 % (ou, cn))
  1810.  
  1811.         groupDN = 'CN=%s,%s,%s' % (cn, (groupOU or 'CN=Users'), domainDN)
  1812.    
  1813.         ldbMessage = {}
  1814.         # Standard Samba Account Information
  1815.         if (groupType is not None) and (groupScope is not None):
  1816.             gType = self.GROUPTYPES.get(groupType.upper())
  1817.             if gType is not None:
  1818.                 gType = gType.get(groupScope.upper())
  1819.             if gType is not None:
  1820.                 ldbMessage['groupType'] = gType
  1821.  
  1822.         if displayName is not None:
  1823.             ldbMessage['displayName'] = displayName
  1824.  
  1825.         if description is not None:
  1826.             ldbMessage['description'] = description
  1827.  
  1828.         if mail is not None:
  1829.             ldbMessage['mail'] = mail
  1830.  
  1831.         if info is not None:
  1832.             ldbMessage['info'] = info
  1833.  
  1834.         if sd is not None:
  1835.             ldbMessage['nTSecurityDescriptor'] = ndr_pack(sd)
  1836.  
  1837.         # Posix Attributes
  1838.         if gidNumber is not None:
  1839.             ldbMessage['gidNumber'] = '%s' % gidNumber
  1840.  
  1841.         # Additional Attributes
  1842.         for (extraKey, extraValue) in extraAttrs.items():
  1843.             ldbMessage[extraKey] = extraValue
  1844.  
  1845.         # Modify Group
  1846.         if ldbMessage is not {}:
  1847.             self._modifyFromDict(groupDN, ldbMessage, attrs=attrs)
  1848.  
  1849.     def renameADGroup(self, cn, newCN, newGroupOU=None,
  1850.                       url=None, username=None, realm=None, password=None,
  1851.                       lpOptions=None):
  1852.         if self._connection is None:
  1853.             self.connect(url=url, username=username, realm=realm,
  1854.                          password=password, lpOptions=lpOptions)
  1855.  
  1856.         domainDN = self._getDomainDN()
  1857.  
  1858.         expression = ('(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))'
  1859.                       % (ldb.binary_encode(cn),
  1860.                          ldb.binary_encode(cn),
  1861.                          "CN=Group,CN=Schema,CN=Configuration",
  1862.                          domainDN))
  1863.         adGroup = self._search(expression=expression, attrs=None)
  1864.         try:
  1865.             assert(len(adGroup) == 1)
  1866.         except AssertionError:
  1867.             raise Exception('Unable to find group %s.' % cn)
  1868.  
  1869.         oldGroupDN = adGroup[0].dn
  1870.  
  1871.         if newGroupOU is not None:
  1872.             if not self.existsOU(newGroupOU):
  1873.                 raise Exception(
  1874.                     'Cannot add group %s to non existing Organizational Unit %s.'
  1875.                     % (cn, newGroupOU))
  1876.        
  1877.         newGroupDN = 'CN=%s,%s,%s' % (newCN,
  1878.                                       (newGroupOU or 'CN=Users'),
  1879.                                       domainDN)
  1880.  
  1881.         self._connection.transaction_start()
  1882.         try:
  1883.             self._connection.rename(oldGroupDN, newGroupDN)
  1884.             self.modifyADGroup(newCN,
  1885.                                extraAttrs={'sAMAccountName': newCN})
  1886.         except:
  1887.             self._connection.transaction_cancel()
  1888.             raise
  1889.         self._connection.transaction_commit()
  1890.  
  1891.     def deleteADGroup(self, cn,
  1892.                       url=None, username=None, realm=None, password=None,
  1893.                       lpOptions=None):
  1894.         """Deletes a group
  1895.  
  1896.        """
  1897.         if self._connection is None:
  1898.             self.connect(url=url, username=username, realm=realm,
  1899.                          password=password, lpOptions=lpOptions)
  1900.  
  1901.         domainDN = self._getDomainDN()
  1902.  
  1903.         expression = "(&(|(sAMAccountName=%s)(CN=%s))(objectCategory=%s,%s))" % (
  1904.             ldb.binary_encode(cn),
  1905.             ldb.binary_encode(cn),
  1906.             "CN=Group,CN=Schema,CN=Configuration",
  1907.             domainDN)
  1908.         adGroup = self._search(expression=expression, attrs=None)
  1909.  
  1910.         try:
  1911.             assert(len(adGroup) == 1)
  1912.         except AssertionError:
  1913.             raise Exception('Unable to find group %s.' % cn)
  1914.         self._connection.delete(adGroup[0].dn)
  1915.            
  1916.     def addADGroupMembers(self, cn, adUsersList,
  1917.                           url=None, username=None, realm=None, password=None,
  1918.                           lpOptions=None):
  1919.        
  1920.         self._addRemoveADGroupMembers(cn, adUsersList,
  1921.             url=url, username=username, realm=realm, password=password,
  1922.             lpOptions=lpOptions)
  1923.  
  1924.     def removeADGroupMembers(self, cn, adUsersList,
  1925.                              url=None, username=None, realm=None, password=None,
  1926.                              lpOptions=None):
  1927.        
  1928.         self._addRemoveADGroupMembers(cn, adUsersList,
  1929.             addOperation=False,
  1930.             url=url, username=username, realm=realm, password=password,
  1931.             lpOptions=lpOptions)
  1932.  
  1933. ###
  1934.  
  1935. from pprint import pprint
  1936.  
  1937. url = 'ldap://localhost:389/'
  1938. username = 'administrator'
  1939. realm = 'EXAMPLE.COM'
  1940. password = 'test'
  1941.  
  1942. lpOptions = dict(
  1943.     realm = realm,
  1944.     workgroup = 'WORKGROUP',
  1945. )
  1946.  
  1947. adUser = ADUser()
  1948.  
  1949. adUser.connect(url=url, username=username, realm=realm, password=password,
  1950.                lpOptions=lpOptions)
  1951.  
  1952. pprint (adUser.getADUser('administrator'))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement