[pywin32-checkins] pywin32/com/win32comext/adsi/demos scp.py,1.1,1.2
OLD project page for the Python extensions for Windows
Brought to you by:
mhammond
From: Mark H. <mha...@us...> - 2005-05-25 02:11:25
|
Update of /cvsroot/pywin32/pywin32/com/win32comext/adsi/demos In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv13773 Modified Files: scp.py Log Message: Beef up this demo significantly Index: scp.py =================================================================== RCS file: /cvsroot/pywin32/pywin32/com/win32comext/adsi/demos/scp.py,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** scp.py 8 Oct 2004 23:37:27 -0000 1.1 --- scp.py 25 May 2005 02:11:15 -0000 1.2 *************** *** 1,12 **** ! # re-implementation of the MS "Scp" sample functions ! # Adds and removes an ActiveDirectory "Service Connection Point", ! # including managing the security on the object. ! # This is likely to become a 'module' rather than a demo, once ! # we had reasonable Python signatures for the functions ! # (ie, once we have one, real, service that uses it :) from win32com.adsi.adsicon import * from win32com.adsi import adsi ! import win32api, win32con from win32com.client import Dispatch # Returns distinguished name of SCP. --- 1,58 ---- ! """A re-implementation of the MS DirectoryService samples related to services. ! ! * Adds and removes an ActiveDirectory "Service Connection Point", ! including managing the security on the object. ! * Creates and registers Service Principal Names. ! * Changes the username for a domain user. ! ! Some of these functions are likely to become move to a module - but there ! is also a little command-line-interface to try these functions out. ! ! For example: ! ! scp.py --account-name=domain\user --service-class=PythonScpTest \\ ! --keyword=foo --keyword=bar --binding-string=bind_info \\ ! ScpCreate SpnCreate SpnRegister ! ! would: ! * Attempt to delete a Service Connection Point for the service class ! 'PythonScpTest' ! * Attempt to create a Service Connection Point for that class, with 2 ! keywords and a binding string of 'bind_info' ! * Create a Service Principal Name for the service and register it ! ! to undo those changes, you could execute: ! ! scp.py --account-name=domain\user --service-class=PythonScpTest \\ ! SpnCreate SpnUnregister ScpDelete ! ! which will: ! * Create a SPN ! * Unregister that SPN from the Active Directory. ! * Delete the Service Connection Point ! ! Executing with --test will create and remove one of everything. ! """ ! from win32com.adsi.adsicon import * from win32com.adsi import adsi ! import win32api, win32con, winerror from win32com.client import Dispatch + import ntsecuritycon as dscon + import win32security + import optparse, textwrap + import traceback + import types + + verbose = 1 + g_createdSCP = None + g_createdSPNs = [] + g_createdSPNLast = None + + import logging + + logger = logging # use logging module global methods for now. + + # still a bit confused about log(n, ...) vs logger.info/debug() # Returns distinguished name of SCP. *************** *** 20,27 **** dns_name_type = "A", dn = None, ): container_name = container_name or service_class_name ! # Get the DNS name of the local computer ! dns_name = win32api.GetComputerNameEx(win32con.ComputerNameDnsFullyQualified) # Get the distinguished name of the computer object for the local computer if dn is None: --- 66,75 ---- dns_name_type = "A", dn = None, + dns_name = None, ): container_name = container_name or service_class_name ! if not dns_name: ! # Get the DNS name of the local computer ! dns_name = win32api.GetComputerNameEx(win32con.ComputerNameDnsFullyQualified) # Get the distinguished name of the computer object for the local computer if dn is None: *************** *** 32,36 **** # Publish the SCP as a child of the computer object - keywords = keywords or [] # Fill in the attribute values to be stored in the SCP. --- 80,83 ---- *************** *** 45,52 **** ] new = comp.CreateDSObject("cn=" + container_name, attrs) ! print "got new", new # Wrap in a usable IDispatch object. new = Dispatch(new) AllowAccessToScpProperties(account_name, new) def ScpDelete(service_class_name, container_name = None, dn = None): --- 92,101 ---- ] new = comp.CreateDSObject("cn=" + container_name, attrs) ! logger.info("New connection point is at %s", container_name) # Wrap in a usable IDispatch object. new = Dispatch(new) + # And allow access to the SCP for the specified account name AllowAccessToScpProperties(account_name, new) + return new def ScpDelete(service_class_name, container_name = None, dn = None): *************** *** 54,70 **** if dn is None: dn = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN) ! print "DN is", dn # Compose the ADSpath and bind to the computer object for the local computer comp = adsi.ADsGetObject("LDAP://" + dn, adsi.IID_IDirectoryObject) comp.DeleteDSObject("cn=" + container_name) ! print "Deleted!" def AllowAccessToScpProperties( accountSAM, #Service account to allow access. ! scpObject): # The IADs SCP object. - attribute = "nTSecurityDescriptor"; - # If no service account is specified, service runs under LocalSystem. # So allow access to the computer account of the service's host. --- 103,138 ---- if dn is None: dn = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN) ! logger.debug("Removing connection point from %s", dn) # Compose the ADSpath and bind to the computer object for the local computer comp = adsi.ADsGetObject("LDAP://" + dn, adsi.IID_IDirectoryObject) comp.DeleteDSObject("cn=" + container_name) ! logger.info("Deleted service connection point '%s'", container_name) ! ! # This function is described in detail in the MSDN article titled ! # "Enabling Service Account to Access SCP Properties" ! # From that article: ! # The following sample code sets a pair of ACEs on a service connection point ! # (SCP) object. The ACEs grant read/write access to the user or computer account ! # under which the service instance will be running. Your service installation ! # program calls this code to ensure that the service will be allowed to update ! # its properties at run time. If you don't set ACEs like these, your service ! # will get access-denied errors if it tries to modify the SCP's properties. ! # ! # The code uses the IADsSecurityDescriptor, IADsAccessControlList, and ! # IADsAccessControlEntry interfaces to do the following: ! # * Get the SCP object's security descriptor. ! # * Set ACEs in the DACL of the security descriptor. ! # * Set the security descriptor back on the SCP object. def AllowAccessToScpProperties( accountSAM, #Service account to allow access. ! scpObject, # The IADs SCP object. ! schemaIDGUIDs = # Attributes to allow write-access to. ! ("{28630eb8-41d5-11d1-a9c1-0000f80367c1}", # serviceDNSName ! "{b7b1311c-b82e-11d0-afee-0000f80367c1}", # serviceBindingInformation ! ) ! ): # If no service account is specified, service runs under LocalSystem. # So allow access to the computer account of the service's host. *************** *** 75,119 **** trustee = win32api.GetComputerObjectName(win32con.NameSamCompatible) ! # Get the nTSecurityDescriptor sd = getattr(scpObject, attribute) - acl = sd.DiscretionaryAcl ! ace1 = Dispatch(adsi.CLSID_AccessControlEntry) ! ace2 = Dispatch(adsi.CLSID_AccessControlEntry) - # Set the properties of the two ACEs. - - # Allow read and write access to the property. - ace1.AccessMask = ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP - ace2.AccessMask = ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP - - # Set the trustee, which is either the service account or the - # host computer account. - ace1.Trustee = trustee - ace2.Trustee = trustee - - # Set the ACE type. - ace1.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT - ace2.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT - - # Set AceFlags to zero because ACE is not inheritable. - ace1.AceFlags = 0 - ace2.AceFlags = 0 - - # Set Flags to indicate an ACE that protects a specified object. - ace1.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT - ace2.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT - - # Set ObjectType to the schemaIDGUID of the attribute. - ace1.ObjectType = "{28630eb8-41d5-11d1-a9c1-0000f80367c1}" # serviceDNSName - ace2.ObjectType = "{b7b1311c-b82e-11d0-afee-0000f80367c1}" # serviceBindingInformation - - # Add the ACEs to the DACL. - acl.AddAce(ace1) - - # Do it again for the second ACE. - acl.AddAce(ace2) - # Write the modified DACL back to the security descriptor. sd.DiscretionaryAcl = acl --- 143,177 ---- trustee = win32api.GetComputerObjectName(win32con.NameSamCompatible) ! # Get the nTSecurityDescriptor attribute ! attribute = "nTSecurityDescriptor" sd = getattr(scpObject, attribute) acl = sd.DiscretionaryAcl ! for sguid in schemaIDGUIDs: ! ace = Dispatch(adsi.CLSID_AccessControlEntry) ! ! # Set the properties of the ACE. ! # Allow read and write access to the property. ! ace.AccessMask = ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP ! ! # Set the trustee, which is either the service account or the ! # host computer account. ! ace.Trustee = trustee ! ! # Set the ACE type. ! ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT ! ! # Set AceFlags to zero because ACE is not inheritable. ! ace.AceFlags = 0 ! ! # Set Flags to indicate an ACE that protects a specified object. ! ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT ! ! # Set ObjectType to the schemaIDGUID of the attribute. ! ace.ObjectType = sguid ! ! # Add the ACEs to the DACL. ! acl.AddAce(ace) # Write the modified DACL back to the security descriptor. sd.DiscretionaryAcl = acl *************** *** 122,128 **** # SetInfo updates the SCP object in the directory. scpObject.SetInfo() ! print "Set security on object for account '%s'" % (trustee,) if __name__=='__main__': ! ScpDelete("PythonSCPTest") ! ScpCreate("2222", "PythonSCPTest", None, keywords = "mark was here".split()) --- 180,492 ---- # SetInfo updates the SCP object in the directory. scpObject.SetInfo() ! logger.info("Set security on object for account '%s'" % (trustee,)) ! ! # Service Principal Names functions from the same sample. ! # The example calls the DsWriteAccountSpn function, which stores the SPNs in ! # Microsoft Active Directory under the servicePrincipalName attribute of the ! # account object specified by the serviceAcctDN parameter. The account object ! # corresponds to the logon account specified in the CreateService call for this ! # service instance. If the logon account is a domain user account, ! # serviceAcctDN must be the distinguished name of the account object in ! # Active Directory for that user account. If the service's logon account is the ! # LocalSystem account, serviceAcctDN must be the distinguished name of the ! # computer account object for the host computer on which the service is ! # installed. win32api.TranslateNames and win32security.DsCrackNames can ! # be used to convert a domain\account format name to a distinguished name. ! def SpnRegister( ! serviceAcctDN, # DN of the service's logon account ! spns, # List of SPNs to register ! operation, # Add, replace, or delete SPNs ! ): ! assert spns not in types.StringTypes and hasattr(spns, "__iter__"), \ ! "spns must be a sequence of strings (got %r)" % spns ! # Bind to a domain controller. ! # Get the domain for the current user. ! samName = win32api.GetUserNameEx(win32api.NameSamCompatible) ! samName = samName.split('\\', 1)[0] ! ! if not serviceAcctDN: ! # Get the SAM account name of the computer object for the server. ! serviceAcctDN = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN) ! logger.debug("SpnRegister using DN '%s'", serviceAcctDN) ! ! # Get the name of a domain controller in that domain. ! info = win32security.DsGetDcName( ! domainName=samName, ! flags=dscon.DS_IS_FLAT_NAME | ! dscon.DS_RETURN_DNS_NAME | ! dscon.DS_DIRECTORY_SERVICE_REQUIRED) ! # Bind to the domain controller. ! handle = win32security.DsBind( info['DomainControllerName'] ) ! ! # Write the SPNs to the service account or computer account. ! logger.debug("DsWriteAccountSpn with spns %s") ! win32security.DsWriteAccountSpn( ! handle, # handle to the directory ! operation, # Add or remove SPN from account's existing SPNs ! serviceAcctDN, # DN of service account or computer account ! spns) # names ! ! # Unbind the DS in any case (but Python would do it anyway) ! handle.Close() ! ! def UserChangePassword(username_dn, new_password): ! # set the password on the account. ! # Use the distinguished name to bind to the account object. ! accountPath = "LDAP://" + username_dn ! user = adsi.ADsGetObject(accountPath, adsi.IID_IADsUser) ! ! # Set the password on the account. ! user.SetPassword(new_password) ! ! # functions related to the command-line interface ! def log(level, msg, *args): ! if verbose >= level: ! print msg % args ! ! class _NoDefault: pass ! ! def _get_option(po, opt_name, default = _NoDefault): ! parser, options = po ! ret = getattr(options, opt_name, default) ! if not ret and default is _NoDefault: ! parser.error("The '%s' option must be specified for this operation" % opt_name) ! if not ret: ! ret = default ! return ret ! ! def _option_error(po, why): ! parser = po[0] ! parser.error(why) ! ! def do_ScpCreate(po): ! """Create a Service Connection Point""" ! global g_createdSCP ! scp = ScpCreate(_get_option(po, "binding_string"), ! _get_option(po, "service_class"), ! _get_option(po, "account_name_sam", None), ! keywords=_get_option(po, "keywords", None)) ! g_createdSCP = scp ! return scp.distinguishedName ! ! def do_ScpDelete(po): ! """Delete a Service Connection Point""" ! sc = _get_option(po, "service_class") ! try: ! ScpDelete(sc) ! except adsi.error, details: ! if details[0] != winerror.ERROR_DS_OBJ_NOT_FOUND: ! raise ! log(2, "ScpDelete ignoring ERROR_DS_OBJ_NOT_FOUND for service-class '%s'", ! sc) ! return sc ! ! def do_SpnCreate(po): ! """Create a Service Principal Name""" ! # The 'service name' is the dn of our scp. ! if g_createdSCP is None: ! # Could accept an arg to avoid this? ! _option_error(po, "ScpCreate must have been specified before SpnCreate") ! # Create a Service Principal Name" ! spns = win32security.DsGetSpn(dscon.DS_SPN_SERVICE, ! _get_option(po, "service_class"), ! g_createdSCP.distinguishedName, ! _get_option(po, "port", 0), ! None, None) ! spn = spns[0] ! log(2, "Created SPN: %s", spn) ! global g_createdSPNLast ! g_createdSPNLast = spn ! g_createdSPNs.append(spn) ! return spn ! ! def do_SpnRegister(po): ! """Register a previously created Service Principal Name""" ! if not g_createdSPNLast: ! _option_error(po, "SpnCreate must appear before SpnRegister") ! ! SpnRegister(_get_option(po, "account_name_dn", None), ! (g_createdSPNLast,), ! dscon.DS_SPN_ADD_SPN_OP) ! return g_createdSPNLast ! ! def do_SpnUnregister(po): ! """Unregister a previously created Service Principal Name""" ! if not g_createdSPNLast: ! _option_error(po, "SpnCreate must appear before SpnUnregister") ! SpnRegister(_get_option(po, "account_name_dn", None), ! (g_createdSPNLast,), ! dscon.DS_SPN_DELETE_SPN_OP) ! return g_createdSPNLast ! ! def do_UserChangePassword(po): ! """Change the password for a specified user""" ! UserChangePassword(_get_option(po, "account_name_dn"), ! _get_option(po, "password")) ! return "Password changed OK" ! ! handlers = ( ! ('ScpCreate', do_ScpCreate), ! ('ScpDelete', do_ScpDelete), ! ('SpnCreate', do_SpnCreate), ! ('SpnRegister', do_SpnRegister), ! ('SpnUnregister', do_SpnUnregister), ! ('UserChangePassword', do_UserChangePassword), ! ) ! ! class HelpFormatter(optparse.IndentedHelpFormatter): ! def format_description(self, description): ! return description ! ! def main(): ! global verbose ! _handlers_dict = {} ! ! arg_descs = [] ! for arg, func in handlers: ! this_desc = "\n".join(textwrap.wrap(func.__doc__, ! subsequent_indent = " " * 8)) ! arg_descs.append(" %s: %s" % (arg, this_desc)) ! _handlers_dict[arg.lower()] = func ! ! description = __doc__ + "\ncommands:\n" + "\n".join(arg_descs) + "\n" ! ! parser = optparse.OptionParser(usage = "%prog [options] command ...", ! description=description, ! formatter=HelpFormatter()) ! ! parser.add_option("-v", action="count", ! dest="verbose", default=1, ! help="increase the verbosity of status messages") ! ! parser.add_option("-q", "--quiet", action="store_true", ! help="Don't print any status messages") ! ! parser.add_option("-t", "--test", action="store_true", ! help="Execute a mini-test suite, providing defaults for most options and args"), ! ! parser.add_option("", "--show-tracebacks", action="store_true", ! help="Show the tracebacks for any exceptions") ! ! parser.add_option("", "--service-class", ! help="The service class name to use") ! ! parser.add_option("", "--port", default=0, ! help="The port number to associate with the SPN") ! ! parser.add_option("", "--binding-string", ! help="The binding string to use for SCP creation") ! ! parser.add_option("", "--account-name", ! help="The account name to use (default is LocalSystem)") ! ! parser.add_option("", "--password", ! help="The password to set.") ! ! parser.add_option("", "--keyword", action="append", dest="keywords", ! help="""A keyword to add to the SCP. May be specified ! multiple times""") ! ! parser.add_option("", "--log-level", ! help="""The log-level to use - may be a number or a logging ! module constant""", default=str(logging.WARNING)) ! ! options, args = parser.parse_args() ! po = (parser, options) ! # fixup misc ! try: ! options.port = int(options.port) ! except (TypeError, ValueError): ! parser.error("--port must be numeric") ! # fixup log-level ! try: ! log_level = int(options.log_level) ! except (TypeError, ValueError): ! try: ! log_level = int(getattr(logging, options.log_level.upper())) ! except (ValueError, TypeError, AttributeError): ! parser.error("Invalid --log-level value") ! try: ! sl = logger.setLevel ! # logger is a real logger ! except AttributeError: ! # logger is logging module ! sl = logging.getLogger().setLevel ! sl(log_level) ! # Check -q/-v ! if options.quiet and options.verbose: ! parser.error("Can't specify --quiet and --verbose") ! if options.quiet: ! options.verbose -= 1 ! verbose = options.verbose ! # --test ! if options.test: ! if args: ! parser.error("Can't specify args with --test") ! ! args = "ScpDelete ScpCreate SpnCreate SpnRegister SpnUnregister ScpDelete" ! log(1, "--test - pretending args are:\n %s", args) ! args = args.split() ! if not options.service_class: ! options.service_class = "PythonScpTest" ! log(2, "--test: --service-class=%s", options.service_class) ! if not options.keywords: ! options.keywords = "Python Powered".split() ! log(2, "--test: --keyword=%s", options.keywords) ! if not options.binding_string: ! options.binding_string = "test binding string" ! log(2, "--test: --binding-string=%s", options.binding_string) ! ! # check args ! if not args: ! parser.error("No command specified (use --help for valid commands)") ! for arg in args: ! if arg.lower() not in _handlers_dict: ! parser.error("Invalid command '%s' (use --help for valid commands)" % arg) ! ! # Patch up account-name. ! if options.account_name: ! log(2, "Translating account name '%s'", options.account_name) ! options.account_name_sam = win32security.TranslateName(options.account_name, ! win32api.NameUnknown, ! win32api.NameSamCompatible) ! log(2, "NameSamCompatible is '%s'",options.account_name_sam) ! options.account_name_dn = win32security.TranslateName(options.account_name, ! win32api.NameUnknown, ! win32api.NameFullyQualifiedDN) ! log(2, "NameFullyQualifiedDNis '%s'",options.account_name_dn) ! ! # do it. ! for arg in args: ! handler = _handlers_dict[arg.lower()] # already been validated ! if handler is None: ! parser.error("Invalid command '%s'" % arg) ! err_msg = None ! try: ! try: ! log(2, "Executing '%s'...", arg) ! result = handler(po) ! log(1, "%s: %s", arg, result) ! except: ! if options.show_tracebacks: ! print "--show-tracebacks specified - dumping exception" ! traceback.print_exc() ! raise ! except adsi.error, (hr, desc, exc, argerr): ! if exc: ! extra_desc = exc[2] ! else: ! extra_desc = "" ! err_msg = desc ! if extra_desc: ! err_msg += "\n\t" + extra_desc ! except win32api.error, (hr, func, msg): ! err_msg = msg ! if err_msg: ! log(1, "Command '%s' failed: %s", arg, err_msg) if __name__=='__main__': ! try: ! main() ! except KeyboardInterrupt: ! print "*** Interrupted" |