61717678cd4c6197abd4940e0ab846544797b0f4
[samba.git] / python / samba / netcmd / ou.py
1 # implement samba_tool ou commands
2 #
3 # Copyright Bjoern Baumbach <bb@sernet.de> 2018
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import samba.getopt as options
20 import ldb
21
22 from samba.auth import system_session
23 from samba.netcmd import (
24     Command,
25     CommandError,
26     Option,
27     SuperCommand,
28     )
29 from samba.samdb import SamDB
30 from samba import dsdb
31 from operator import attrgetter
32
33 class cmd_rename(Command):
34     """Rename an organizational unit.
35
36     The name of the organizational units can be specified as a full DN
37     or without the domainDN component.
38
39     Examples:
40     samba-tool ou rename 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \
41         'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com'
42     samba-tool ou rename 'OU=OrgUnit' 'OU=NewNameOfOrgUnit'
43
44     The examples show how an administrator would rename an ou 'OrgUnit'
45     to 'NewNameOfOrgUnit'. The new DN would be
46     'OU=NewNameOfOrgUnit,DC=samdom,DC=example,DC=com'
47     """
48
49     synopsis = "%prog <old_ou_dn> <new_ou_dn> [options]"
50
51     takes_options = [
52         Option("-H", "--URL", help="LDB URL for database or target server",
53                type=str, metavar="URL", dest="H"),
54     ]
55
56     takes_args = ["old_ou_dn", "new_ou_dn"]
57     takes_optiongroups = {
58         "sambaopts": options.SambaOptions,
59         "credopts": options.CredentialsOptions,
60         "versionopts": options.VersionOptions,
61         }
62
63     def run(self, old_ou_dn, new_ou_dn, credopts=None, sambaopts=None,
64             versionopts=None, H=None):
65         lp = sambaopts.get_loadparm()
66         creds = credopts.get_credentials(lp, fallback_machine=True)
67         samdb = SamDB(url=H, session_info=system_session(),
68                       credentials=creds, lp=lp)
69         domain_dn = ldb.Dn(samdb, samdb.domain_dn())
70
71         try:
72             full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn)
73         except Exception as e:
74             raise CommandError('Invalid old_ou_dn "%s": %s' %
75                                (old_ou_dn, e.message))
76         try:
77             full_new_ou_dn = samdb.normalize_dn_in_domain(new_ou_dn)
78         except Exception as e:
79             raise CommandError('Invalid new_ou_dn "%s": %s' %
80                                (new_ou_dn, e.message))
81
82         try:
83             res = samdb.search(base=full_old_ou_dn,
84                                expression="(objectclass=organizationalUnit)",
85                                scope=ldb.SCOPE_BASE, attrs=[])
86             if len(res) == 0:
87                 self.outf.write('Unable to find ou "%s"\n' % old_ou_dn)
88                 return
89
90             samdb.rename(full_old_ou_dn, full_new_ou_dn)
91         except Exception as e:
92             raise CommandError('Failed to rename ou "%s"' % full_old_ou_dn, e)
93         self.outf.write('Renamed ou "%s" to "%s"\n' % (full_old_ou_dn,
94                                                        full_new_ou_dn))
95
96 class cmd_move(Command):
97     """Move an organizational unit.
98
99     The name of the organizational units can be specified as a full DN
100     or without the domainDN component.
101
102     Examples:
103     samba-tool ou move 'OU=OrgUnit,DC=samdom,DC=example,DC=com' \
104         'OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com'
105     samba-tool ou rename 'OU=OrgUnit' 'OU=NewParentOfOrgUnit'
106
107     The examples show how an administrator would move an ou 'OrgUnit'
108     into the ou 'NewParentOfOrgUnit'. The ou 'OrgUnit' would become
109     a child of the 'NewParentOfOrgUnit' ou. The new DN would be
110     'OU=OrgUnit,OU=NewParentOfOrgUnit,DC=samdom,DC=example,DC=com'
111     """
112
113     synopsis = "%prog <old_ou_dn> <new_parent_dn> [options]"
114
115     takes_options = [
116         Option("-H", "--URL", help="LDB URL for database or target server",
117                type=str, metavar="URL", dest="H"),
118     ]
119
120     takes_args = ["old_ou_dn", "new_parent_dn"]
121     takes_optiongroups = {
122         "sambaopts": options.SambaOptions,
123         "credopts": options.CredentialsOptions,
124         "versionopts": options.VersionOptions,
125         }
126
127     def run(self, old_ou_dn, new_parent_dn, credopts=None, sambaopts=None,
128             versionopts=None, H=None):
129         lp = sambaopts.get_loadparm()
130         creds = credopts.get_credentials(lp, fallback_machine=True)
131         samdb = SamDB(url=H, session_info=system_session(),
132                       credentials=creds, lp=lp)
133
134         domain_dn = ldb.Dn(samdb, samdb.domain_dn())
135         try:
136             full_old_ou_dn = samdb.normalize_dn_in_domain(old_ou_dn)
137         except Exception as e:
138             raise CommandError('Invalid old_ou_dn "%s": %s' %
139                                (old_ou_dn, e.message))
140         try:
141             full_new_parent_dn = samdb.normalize_dn_in_domain(new_parent_dn)
142         except Exception as e:
143             raise CommandError('Invalid new_parent_dn "%s": %s' %
144                                (new_parent_dn, e.message))
145
146         full_new_ou_dn = ldb.Dn(samdb, str(full_old_ou_dn))
147         full_new_ou_dn.remove_base_components(len(full_old_ou_dn)-1)
148         full_new_ou_dn.add_base(full_new_parent_dn)
149
150         try:
151             res = samdb.search(base=full_old_ou_dn,
152                                expression="(objectclass=organizationalUnit)",
153                                scope=ldb.SCOPE_BASE, attrs=[])
154             if len(res) == 0:
155                 self.outf.write('Unable to find ou "%s"\n' % full_old_ou_dn)
156                 return
157             samdb.rename(full_old_ou_dn, full_new_ou_dn)
158         except Exception as e:
159             raise CommandError('Failed to move ou "%s"' % full_old_ou_dn, e)
160         self.outf.write('Moved ou "%s" into "%s"\n' %
161                         (full_old_ou_dn, full_new_parent_dn))
162
163 class cmd_create(Command):
164     """Create an organizational unit.
165
166     The name of the new ou can be specified as a full DN or without the
167     domainDN component.
168
169     Examples:
170     samba-tool ou create 'OU=OrgUnit'
171     samba-tool ou create 'OU=SubOU,OU=OrgUnit,DC=samdom,DC=example,DC=com'
172
173     The examples show how an administrator would create a new ou 'OrgUnit'
174     and a new ou 'SubOU' as a child of the ou 'OrgUnit'.
175     """
176
177     synopsis = "%prog <ou_dn> [options]"
178
179     takes_options = [
180         Option("-H", "--URL", help="LDB URL for database or target server",
181                type=str, metavar="URL", dest="H"),
182         Option("--description", help="OU's description",
183                type=str, dest="description"),
184     ]
185
186     takes_args = ["ou_dn"]
187     takes_optiongroups = {
188         "sambaopts": options.SambaOptions,
189         "credopts": options.CredentialsOptions,
190         "versionopts": options.VersionOptions,
191         }
192
193     def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
194             H=None, description=None):
195         lp = sambaopts.get_loadparm()
196         creds = credopts.get_credentials(lp, fallback_machine=True)
197         samdb = SamDB(url=H, session_info=system_session(),
198                       credentials=creds, lp=lp)
199
200         try:
201             full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
202         except Exception as e:
203             raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
204
205         try:
206             samdb.create_ou(full_ou_dn, description=description)
207         except Exception as e:
208             raise CommandError('Failed to create ou "%s"' % full_ou_dn, e)
209
210         self.outf.write('Created ou "%s"\n' % full_ou_dn)
211
212 class cmd_listobjects(Command):
213     """List all objects in an organizational unit.
214
215     The name of the organizational unit can be specified as a full DN
216     or without the domainDN component.
217
218     Examples:
219     samba-tool ou listobjects 'OU=OrgUnit,DC=samdom,DC=example,DC=com'
220     samba-tool ou listobjects 'OU=OrgUnit'
221
222     The examples show how an administrator would list all child objects
223     of the ou 'OrgUnit'.
224     """
225     synopsis = "%prog <ou_dn> [options]"
226
227     takes_options = [
228         Option("-H", "--URL", help="LDB URL for database or target server",
229                type=str, metavar="URL", dest="H"),
230         Option("--full-dn", dest="full_dn", default=False, action='store_true',
231                help="Display DNs including the base DN."),
232         Option("-r", "--recursive", dest="recursive", default=False,
233                action='store_true', help="List objects recursively."),
234     ]
235
236     takes_args = [ "ou_dn" ]
237     takes_optiongroups = {
238         "sambaopts": options.SambaOptions,
239         "credopts": options.CredentialsOptions,
240         "versionopts": options.VersionOptions,
241         }
242
243     def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
244             H=None, full_dn=False, recursive=False):
245         lp = sambaopts.get_loadparm()
246         creds = credopts.get_credentials(lp, fallback_machine=True)
247         samdb = SamDB(url=H, session_info=system_session(),
248                       credentials=creds, lp=lp)
249         domain_dn = ldb.Dn(samdb, samdb.domain_dn())
250
251         try:
252             full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
253         except Exception as e:
254             raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
255
256         minchilds = 0
257         scope = ldb.SCOPE_ONELEVEL
258         if recursive:
259             minchilds = 1
260             scope = ldb.SCOPE_SUBTREE
261
262         try:
263             childs = samdb.search(base=full_ou_dn,
264                                   expression="(objectclass=*)",
265                                   scope=scope, attrs=[])
266             if len(childs) <= minchilds:
267                 self.outf.write('ou "%s" is empty\n' % ou_dn)
268                 return
269
270             for child in sorted(childs, key=attrgetter('dn')):
271                 if child.dn == full_ou_dn:
272                     continue
273                 if not full_dn:
274                     child.dn.remove_base_components(len(domain_dn))
275                 self.outf.write("%s\n" % child.dn)
276
277         except Exception as e:
278             raise CommandError('Failed to list contents of ou "%s"' %
279                                full_ou_dn, e)
280
281 class cmd_list(Command):
282     """List all organizational units.
283
284     Example:
285     samba-tool ou listobjects
286
287     The example shows how an administrator would list all organizational
288     units.
289     """
290
291     synopsis = "%prog [options]"
292
293     takes_options = [
294         Option("-H", "--URL", help="LDB URL for database or target server",
295                type=str, metavar="URL", dest="H"),
296         Option("--full-dn", dest="full_dn", default=False, action='store_true',
297                help="Display DNs including the base DN."),
298         ]
299
300     takes_optiongroups = {
301         "sambaopts": options.SambaOptions,
302         "credopts": options.CredentialsOptions,
303         "versionopts": options.VersionOptions,
304         }
305
306     def run(self, sambaopts=None, credopts=None, versionopts=None, H=None,
307         full_dn=False):
308         lp = sambaopts.get_loadparm()
309         creds = credopts.get_credentials(lp, fallback_machine=True)
310         samdb = SamDB(url=H, session_info=system_session(),
311                       credentials=creds, lp=lp)
312
313         domain_dn = ldb.Dn(samdb, samdb.domain_dn())
314         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
315                            expression="(objectClass=organizationalUnit)",
316                            attrs=[])
317         if (len(res) == 0):
318             return
319
320         for msg in sorted(res, key=attrgetter('dn')):
321             if not full_dn:
322                 msg.dn.remove_base_components(len(domain_dn))
323             self.outf.write("%s\n" % str(msg.dn))
324
325 class cmd_delete(Command):
326     """Delete an organizational unit.
327
328     The name of the organizational unit can be specified as a full DN
329     or without the domainDN component.
330
331     Examples:
332     samba-tool ou delete 'OU=OrgUnit,DC=samdom,DC=example,DC=com'
333     samba-tool ou delete 'OU=OrgUnit'
334
335     The examples show how an administrator would delete the ou 'OrgUnit'.
336     """
337
338     synopsis = "%prog <ou_dn> [options]"
339
340     takes_options = [
341         Option("-H", "--URL", help="LDB URL for database or target server",
342                type=str, metavar="URL", dest="H"),
343         Option("--force-subtree-delete", dest="force_subtree_delete",
344                default=False, action='store_true',
345                help="Delete organizational unit and all children reclusively"),
346     ]
347
348     takes_args = ["ou_dn"]
349     takes_optiongroups = {
350         "sambaopts": options.SambaOptions,
351         "credopts": options.CredentialsOptions,
352         "versionopts": options.VersionOptions,
353         }
354
355     def run(self, ou_dn, credopts=None, sambaopts=None, versionopts=None,
356             H=None, force_subtree_delete=False):
357         lp = sambaopts.get_loadparm()
358         creds = credopts.get_credentials(lp, fallback_machine=True)
359         samdb = SamDB(url=H, session_info=system_session(),
360                       credentials=creds, lp=lp)
361         domain_dn = ldb.Dn(samdb, samdb.domain_dn())
362
363         try:
364             full_ou_dn = samdb.normalize_dn_in_domain(ou_dn)
365         except Exception as e:
366             raise CommandError('Invalid ou_dn "%s": %s' % (ou_dn, e.message))
367
368         controls = []
369         if force_subtree_delete:
370             controls = ["tree_delete:1"]
371
372         try:
373             res = samdb.search(base=full_ou_dn,
374                                expression="(objectclass=organizationalUnit)",
375                                scope=ldb.SCOPE_BASE, attrs=[])
376             if len(res) == 0:
377                 self.outf.write('Unable to find ou "%s"\n' % ou_dn)
378                 return
379             samdb.delete(full_ou_dn, controls)
380         except Exception as e:
381             raise CommandError('Failed to delete ou "%s"' % full_ou_dn, e)
382
383         self.outf.write('Deleted ou "%s"\n' % full_ou_dn)
384
385
386 class cmd_ou(SuperCommand):
387     """Organizational Units (OU) management"""
388
389     subcommands = {}
390     subcommands["create"] = cmd_create()
391     subcommands["delete"] = cmd_delete()
392     subcommands["move"] = cmd_move()
393     subcommands["rename"] = cmd_rename()
394     subcommands["list"] = cmd_list()
395     subcommands["listobjects"] = cmd_listobjects()