samba-tool: implement ou management commands
authorBjörn Baumbach <bb@sernet.de>
Thu, 16 Nov 2017 11:31:11 +0000 (12:31 +0100)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 8 Feb 2018 03:58:09 +0000 (04:58 +0100)
Available subcommands:
  create       - Create an organizational unit.
  delete       - Delete an organizational unit.
  list         - List all organizational units
  listobjects  - List all objects in an organizational unit.
  move         - Move an organizational unit.
  rename       - Rename an organizational unit.

Signed-off-by: Björn Baumbach <bb@sernet.de>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
python/samba/netcmd/main.py
python/samba/netcmd/ou.py [new file with mode: 0644]

index 7f94f8978979f02b6cf80dda87f46373bb581d31..a9cf176a8c12164d595be337df4e9e7c517b0791 100644 (file)
@@ -75,5 +75,6 @@ class cmd_sambatool(SuperCommand):
     subcommands["testparm"] = None
     subcommands["time"] = None
     subcommands["user"] = None
+    subcommands["ou"] = None
     subcommands["processes"] = None
     subcommands["visualize"] = None
diff --git a/python/samba/netcmd/ou.py b/python/samba/netcmd/ou.py
new file mode 100644 (file)
index 0000000..16b7f65
--- /dev/null
@@ -0,0 +1,395 @@
+# implement samba_tool ou commands
+#
+# Copyright Bjoern Baumbach <bb@sernet.de> 2018
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import samba.getopt as options
+import ldb
+
+from samba.auth import system_session
+from samba.netcmd import (
+    Command,
+    CommandError,
+    Option,
+    SuperCommand,
+    )
+from samba.samdb import SamDB
+from samba import dsdb
+from operator import attrgetter
+
+class cmd_rename(Command):
+    """Rename an organizational unit.
+
+    The name of the organizational units can be specified as a full DN
+    or without the domainDN component.
+
+    Examples:
+    samba-tool ou rename 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \
+        'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com'
+    samba-tool ou rename 'OU=OrgUnit' 'OU=NewNameOfOrgUnit'
+
+    The examples show how an administrator would rename an ou 'OrgUnit'
+    to 'NewNameOfOrgUnit'. The new DN would be
+    'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com'
+    """
+
+    synopsis = "%prog <old_ou_dn> <new_ou_dn> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+    ]
+
+    takes_args = ["old_ou_dn", "new_ou_dn"]
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, old_ou_dn, new_ou_dn, credopts=None, sambaopts=None,
+            versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+        domain_dn = ldb.Dn(samdb, samdb.domain_dn())
+
+        try:
+            full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid old_ou_dn "%s": %s' %
+                               (old_ou_dn, e.message))
+        try:
+            full_new_ou_dn = samdb.normalize_dn_in_domain(new_ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid new_ou_dn "%s": %s' %
+                               (new_ou_dn, e.message))
+
+        try:
+            res = samdb.search(base=full_old_ou_dn,
+                               expression="(objectclass=organizationalUnit)",
+                               scope=ldb.SCOPE_BASE, attrs=[])
+            if len(res) == 0:
+                self.outf.write('Unable to find ou "%s"\n' % old_ou_dn)
+                return
+
+            samdb.rename(full_old_ou_dn, full_new_ou_dn)
+        except Exception, e:
+            raise CommandError('Failed to rename ou "%s"' % full_old_ou_dn, e)
+        self.outf.write('Renamed ou "%s" to "%s"\n' % (full_old_ou_dn,
+                                                       full_new_ou_dn))
+
+class cmd_move(Command):
+    """Move an organizational unit.
+
+    The name of the organizational units can be specified as a full DN
+    or without the domainDN component.
+
+    Examples:
+    samba-tool ou move 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \
+        'OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com'
+    samba-tool ou rename 'OU=OrgUnit' 'OU=NewParentOfOrgUnit'
+
+    The examples show how an administrator would move an ou 'OrgUnit'
+    into the ou 'NewParentOfOrgUnit'. The ou 'OrgUnit' would become
+    a child of the 'NewParentOfOrgUnit' ou. The new DN would be
+    'OU=OrgUnit,OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com'
+    """
+
+    synopsis = "%prog <old_ou_dn> <new_parent_dn> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+    ]
+
+    takes_args = ["old_ou_dn", "new_parent_dn"]
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, old_ou_dn, new_parent_dn, credopts=None, sambaopts=None,
+            versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        domain_dn = ldb.Dn(samdb, samdb.domain_dn())
+        try:
+            full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid old_ou_dn "%s": %s' %
+                               (old_ou_dn, e.message))
+        try:
+            full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn)
+        except Exception, e:
+            raise CommandError('Invalid new_parent_dn "%s": %s' %
+                               (new_parent_dn, e.message))
+
+        full_new_ou_dn = ldb.Dn(samdb, str(full_old_ou_dn))
+        full_new_ou_dn.remove_base_components(len(full_old_ou_dn)-1)
+        full_new_ou_dn.add_base(full_new_parent_dn)
+
+        try:
+            res = samdb.search(base=full_old_ou_dn,
+                               expression="(objectclass=organizationalUnit)",
+                               scope=ldb.SCOPE_BASE, attrs=[])
+            if len(res) == 0:
+                self.outf.write('Unable to find ou "%s"\n' % full_old_ou_dn)
+                return
+            samdb.rename(full_old_ou_dn, full_new_ou_dn)
+        except Exception, e:
+            raise CommandError('Failed to move ou "%s"' % full_old_ou_dn, e)
+        self.outf.write('Moved ou "%s" into "%s"\n' %
+                        (full_old_ou_dn, full_new_parent_dn))
+
+class cmd_create(Command):
+    """Create an organizational unit.
+
+    The name of the new ou can be specified as a full DN or without the
+    domainDN component.
+
+    Examples:
+    samba-tool ou create 'OU=OrgUnit'
+    samba-tool ou create 'OU=SubOU,OU=OrgUnit,DC=samdom,DC=example,DC=com'
+
+    The examples show how an administrator would create a new ou 'OrgUnit'
+    and a new ou 'SubOU' as a child of the ou 'OrgUnit'.
+    """
+
+    synopsis = "%prog <ou_dn> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        Option("--description", help="OU's description",
+               type=str, dest="description"),
+    ]
+
+    takes_args = ["ou_dn"]
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
+            H=None, description=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        try:
+            full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
+
+        try:
+            samdb.create_ou(full_ou_dn, description=description)
+        except Exception, e:
+            raise CommandError('Failed to create ou "%s"' % full_ou_dn, e)
+
+        self.outf.write('Created ou "%s"\n' % full_ou_dn)
+
+class cmd_listobjects(Command):
+    """List all objects in an organizational unit.
+
+    The name of the organizational unit can be specified as a full DN
+    or without the domainDN component.
+
+    Examples:
+    samba-tool ou listobjects 'OU=OrgUnit,DC=samdom,DC=example,DC=com'
+    samba-tool ou listobjects 'OU=OrgUnit'
+
+    The examples show how an administrator would list all child objects
+    of the ou 'OrgUnit'.
+    """
+    synopsis = "%prog <ou_dn> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        Option("--full-dn", dest="full_dn", default=False, action='store_true',
+               help="Display DNs including the base DN."),
+        Option("-r", "--recursive", dest="recursive", default=False,
+               action='store_true', help="List objects recursively."),
+    ]
+
+    takes_args = [ "ou_dn" ]
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
+            H=None, full_dn=False, recursive=False):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+        domain_dn = ldb.Dn(samdb, samdb.domain_dn())
+
+        try:
+            full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
+
+        minchilds = 0
+        scope = ldb.SCOPE_ONELEVEL
+        if recursive:
+            minchilds = 1
+            scope = ldb.SCOPE_SUBTREE
+
+        try:
+            childs = samdb.search(base=full_ou_dn,
+                                  expression="(objectclass=*)",
+                                  scope=scope, attrs=[])
+            if len(childs) <= minchilds:
+                self.outf.write('ou "%s" is empty\n' % ou_dn)
+                return
+
+            for child in sorted(childs, key=attrgetter('dn')):
+                if child.dn == full_ou_dn:
+                    continue
+                if not full_dn:
+                    child.dn.remove_base_components(len(domain_dn))
+                self.outf.write("%s\n" % child.dn)
+
+        except Exception, e:
+            raise CommandError('Failed to list contents of ou "%s"' %
+                               full_ou_dn, e)
+
+class cmd_list(Command):
+    """List all organizational units.
+
+    Example:
+    samba-tool ou listobjects
+
+    The example shows how an administrator would list all organizational
+    units.
+    """
+
+    synopsis = "%prog [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        Option("--full-dn", dest="full_dn", default=False, action='store_true',
+               help="Display DNs including the base DN."),
+        ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, sambaopts=None, credopts=None, versionopts=None, H=None,
+        full_dn=False):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        domain_dn = ldb.Dn(samdb, samdb.domain_dn())
+        res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
+                           expression="(objectClass=organizationalUnit)",
+                           attrs=[])
+        if (len(res) == 0):
+            return
+
+        for msg in sorted(res, key=attrgetter('dn')):
+            if not full_dn:
+                msg.dn.remove_base_components(len(domain_dn))
+            self.outf.write("%s\n" % str(msg.dn))
+
+class cmd_delete(Command):
+    """Delete an organizational unit.
+
+    The name of the organizational unit can be specified as a full DN
+    or without the domainDN component.
+
+    Examples:
+    samba-tool ou delete 'OU=OrgUnit,DC=samdom,DC=example,DC=com'
+    samba-tool ou delete 'OU=OrgUnit'
+
+    The examples show how an administrator would delete the ou 'OrgUnit'.
+    """
+
+    synopsis = "%prog <ou_dn> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server",
+               type=str, metavar="URL", dest="H"),
+        Option("--force-subtree-delete", dest="force_subtree_delete",
+               default=False, action='store_true',
+               help="Delete organizational unit and all children reclusively"),
+    ]
+
+    takes_args = ["ou_dn"]
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
+            H=None, force_subtree_delete=False):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+        domain_dn = ldb.Dn(samdb, samdb.domain_dn())
+
+        try:
+            full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
+        except Exception, e:
+            raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
+
+        controls = []
+        if force_subtree_delete:
+            controls = ["tree_delete:1"]
+
+        try:
+            res = samdb.search(base=full_ou_dn,
+                               expression="(objectclass=organizationalUnit)",
+                               scope=ldb.SCOPE_BASE, attrs=[])
+            if len(res) == 0:
+                self.outf.write('Unable to find ou "%s"\n' % ou_dn)
+                return
+            samdb.delete(full_ou_dn, controls)
+        except Exception, e:
+            raise CommandError('Failed to delete ou "%s"' % full_ou_dn, e)
+
+        self.outf.write('Deleted ou "%s"\n' % full_ou_dn)
+
+
+class cmd_ou(SuperCommand):
+    """Organizational Units (OU) management"""
+
+    subcommands = {}
+    subcommands["create"] = cmd_create()
+    subcommands["delete"] = cmd_delete()
+    subcommands["move"] = cmd_move()
+    subcommands["rename"] = cmd_rename()
+    subcommands["list"] = cmd_list()
+    subcommands["listobjects"] = cmd_listobjects()