summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/plugins')
-rw-r--r--ipalib/plugins/__init__.py25
-rw-r--r--ipalib/plugins/b_kerberos.py34
-rw-r--r--ipalib/plugins/b_xmlrpc.py102
-rw-r--r--ipalib/plugins/f_automount.py563
-rw-r--r--ipalib/plugins/f_delegation.py65
-rw-r--r--ipalib/plugins/f_group.py384
-rw-r--r--ipalib/plugins/f_host.py286
-rw-r--r--ipalib/plugins/f_hostgroup.py354
-rw-r--r--ipalib/plugins/f_misc.py89
-rw-r--r--ipalib/plugins/f_netgroup.py461
-rw-r--r--ipalib/plugins/f_passwd.py70
-rw-r--r--ipalib/plugins/f_pwpolicy.py122
-rw-r--r--ipalib/plugins/f_ra.py117
-rw-r--r--ipalib/plugins/f_service.py204
-rw-r--r--ipalib/plugins/f_user.py367
15 files changed, 3243 insertions, 0 deletions
diff --git a/ipalib/plugins/__init__.py b/ipalib/plugins/__init__.py
new file mode 100644
index 00000000..544429ef
--- /dev/null
+++ b/ipalib/plugins/__init__.py
@@ -0,0 +1,25 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Sub-package containing all core plugins.
+
+By convention, modules with frontend plugins are named f_*.py and modules
+with backend plugins are named b_*.py.
+"""
diff --git a/ipalib/plugins/b_kerberos.py b/ipalib/plugins/b_kerberos.py
new file mode 100644
index 00000000..cc820497
--- /dev/null
+++ b/ipalib/plugins/b_kerberos.py
@@ -0,0 +1,34 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Backend plugin for Kerberos.
+
+This wraps the python-kerberos and python-krbV bindings.
+"""
+
+from ipalib import api
+from ipalib.backend import Backend
+
+class krb(Backend):
+ """
+ Kerberos backend plugin.
+ """
+
+api.register(krb)
diff --git a/ipalib/plugins/b_xmlrpc.py b/ipalib/plugins/b_xmlrpc.py
new file mode 100644
index 00000000..14f2a9be
--- /dev/null
+++ b/ipalib/plugins/b_xmlrpc.py
@@ -0,0 +1,102 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Backend plugin for XML-RPC client.
+
+This provides a lightwieght XML-RPC client using Python standard library
+``xmlrpclib`` module.
+"""
+
+import xmlrpclib
+import socket
+import httplib
+import kerberos
+from ipalib.backend import Backend
+from ipalib.util import xmlrpc_marshal
+from ipalib import api
+from ipalib import errors
+
+class xmlrpc(Backend):
+ """
+ XML-RPC client backend plugin.
+ """
+
+ def get_client(self):
+ """
+ Return an xmlrpclib.ServerProxy instance (the client).
+ """
+ # FIXME: Rob, is there any reason we can't use allow_none=True here?
+ # Are there any reasonably common XML-RPC client implementations
+ # that don't support the <nil/> extension?
+ # See: http://docs.python.org/library/xmlrpclib.html
+ uri = self.env.xmlrpc_uri
+ if uri.startswith('https://'):
+ return xmlrpclib.ServerProxy(uri,
+ transport=KerbTransport(),
+ )
+ return xmlrpclib.ServerProxy(uri)
+
+ def forward_call(self, name, *args, **kw):
+ """
+ Forward a call over XML-RPC to an IPA server.
+ """
+ self.info('Forwarding %r call to %r' % (name, self.env.xmlrpc_uri))
+ client = self.get_client()
+ command = getattr(client, name)
+ params = xmlrpc_marshal(*args, **kw)
+ try:
+ return command(*params)
+ except socket.error, e:
+ raise
+ except xmlrpclib.Fault, e:
+ err = errors.convertFault(e)
+ raise err
+ return
+
+api.register(xmlrpc)
+
+class KerbTransport(xmlrpclib.SafeTransport):
+ """Handles Kerberos Negotiation authentication to an XML-RPC server."""
+
+ def get_host_info(self, host):
+
+ host, extra_headers, x509 = xmlrpclib.Transport.get_host_info(self, host)
+
+ # Set the remote host principal
+ h = host
+ hostinfo = h.split(':')
+ service = "HTTP@" + hostinfo[0]
+
+ try:
+ rc, vc = kerberos.authGSSClientInit(service);
+ except kerberos.GSSError, e:
+ raise kerberos.GSSError(e)
+
+ try:
+ kerberos.authGSSClientStep(vc, "");
+ except kerberos.GSSError, e:
+ raise kerberos.GSSError(e)
+
+ extra_headers = [
+ ("Authorization", "negotiate %s" % kerberos.authGSSClientResponse(vc) )
+ ]
+
+ return host, extra_headers, x509
diff --git a/ipalib/plugins/f_automount.py b/ipalib/plugins/f_automount.py
new file mode 100644
index 00000000..2365ce22
--- /dev/null
+++ b/ipalib/plugins/f_automount.py
@@ -0,0 +1,563 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for automount.
+
+RFC 2707bis http://www.padl.com/~lukeh/rfc2307bis.txt
+"""
+
+from ldap import explode_dn
+from ipalib import crud, errors
+from ipalib import api, Str, Flag, Object, Command
+
+map_attributes = ['automountMapName', 'description', ]
+key_attributes = ['description', 'automountKey', 'automountInformation']
+
+def display_entry(textui, entry):
+ # FIXME: for now delete dn here. In the future pass in the kw to
+ # output_for_cli()
+ attr = sorted(entry.keys())
+
+ for a in attr:
+ if a != 'dn':
+ textui.print_plain("%s: %s" % (a, entry[a]))
+
+def make_automount_dn(mapname):
+ """
+ Construct automount dn from map name.
+ """
+ # FIXME, should this be in b_ldap?
+ # Experimenting to see what a plugin looks like for a 3rd party who can't
+ # modify the backend.
+ import ldap
+ return 'automountmapname=%s,%s,%s' % (
+ ldap.dn.escape_dn_chars(mapname),
+ api.env.container_automount,
+ api.env.basedn,
+ )
+
+class automount(Object):
+ """
+ Automount object.
+ """
+ takes_params = (
+ Str('automountmapname',
+ cli_name='mapname',
+ primary_key=True,
+ doc='A group of related automount objects',
+ ),
+ )
+api.register(automount)
+
+
+class automount_addmap(crud.Add):
+ 'Add a new automount map.'
+
+ takes_options = (
+ Str('description?',
+ doc='A description of the automount map'),
+ )
+
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-addmap operation.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param mapname: The map name being added.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'automountmapname' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ kw['automountmapname'] = mapname
+ kw['dn'] = make_automount_dn(mapname)
+
+ kw['objectClass'] = ['automountMap']
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, map, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Automount map %s added" % map)
+
+api.register(automount_addmap)
+
+
+class automount_addkey(crud.Add):
+ 'Add a new automount key.'
+ takes_options = (
+ Str('automountkey',
+ cli_name='key',
+ doc='An entry in an automount map'),
+ Str('automountinformation',
+ cli_name='info',
+ doc='Mount information for this key'),
+ Str('description?',
+ doc='A description of the mount'),
+ )
+
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-addkey operation.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param mapname: The map name being added to.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'automountmapname' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ # use find_entry_dn instead of make_automap_dn so we can confirm that
+ # the map exists
+ map_dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ kw['dn'] = "automountkey=%s,%s" % (kw['automountkey'], map_dn)
+
+ kw['objectClass'] = ['automount']
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Automount key added")
+
+api.register(automount_addkey)
+
+
+class automount_delmap(crud.Del):
+ 'Delete an automount map.'
+ def execute(self, mapname, **kw):
+ """Delete an automount map. This will also remove all of the keys
+ associated with this map.
+
+ mapname is the automount map to remove
+
+ :param mapname: The map to be removed
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ keys = api.Command['automount_getkeys'](mapname)
+ if keys:
+ for k in keys:
+ ldap.delete(k.get('dn'))
+ return ldap.delete(dn)
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ print "Automount map and associated keys deleted"
+
+api.register(automount_delmap)
+
+
+class automount_delkey(crud.Del):
+ 'Delete an automount key.'
+ takes_options = (
+ Str('automountkey',
+ cli_name='key',
+ doc='The automount key to remove'),
+ )
+ def execute(self, mapname, **kw):
+ """Delete an automount key.
+
+ key is the automount key to remove
+
+ :param mapname: The automount map containing the key to be removed
+ :param kw: "key" the key to be removed
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ keys = api.Command['automount_getkeys'](mapname)
+ keydn = None
+ keyname = kw.get('automountkey').lower()
+ if keys:
+ for k in keys:
+ if k.get('automountkey').lower() == keyname:
+ keydn = k.get('dn')
+ break
+ if not keydn:
+ raise errors.NotFound
+ return ldap.delete(keydn)
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ print "Automount key deleted"
+
+api.register(automount_delkey)
+
+class automount_modmap(crud.Mod):
+ 'Edit an existing automount map.'
+ takes_options = (
+ Str('description?',
+ doc='A description of the automount map'),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-modmap operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param mapname: The map name to update.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'automountmapname' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ print "Automount map updated"
+
+api.register(automount_modmap)
+
+
+class automount_modkey(crud.Mod):
+ 'Edit an existing automount key.'
+ takes_options = (
+ Str('automountkey',
+ cli_name='key',
+ doc='An entry in an automount map'),
+ Str('automountinformation?',
+ cli_name='info',
+ doc='Mount information for this key'),
+ Str('description?',
+ doc='A description of the automount map'),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-modkey operation.
+
+ Returns the entry
+
+ :param mapname: The map name to update.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'automountmapname' not in kw
+ assert 'dn' not in kw
+ keyname = kw.get('automountkey').lower()
+ del kw['automountkey']
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ keys = api.Command['automount_getkeys'](mapname)
+ keydn = None
+ if keys:
+ for k in keys:
+ if k.get('automountkey').lower() == keyname:
+ keydn = k.get('dn')
+ break
+ if not keydn:
+ raise errors.NotFound
+ return ldap.update(keydn, **kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ print "Automount key updated"
+
+api.register(automount_modkey)
+
+
+class automount_findmap(crud.Find):
+ 'Search automount maps.'
+ takes_options = (
+ Flag('all', doc='Retrieve all attributes'),
+ )
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ search_fields = map_attributes
+
+ for s in search_fields:
+ kw[s] = term
+
+ kw['objectclass'] = 'automountMap'
+ kw['base'] = api.env.container_automount
+ if kw.get('all', False):
+ kw['attributes'] = ['*']
+ else:
+ kw['attributes'] = map_attributes
+ return ldap.search(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ entries = result[1:]
+ if counter == 0:
+ textui.print_plain("No entries found")
+ return
+ elif counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+
+ for e in entries:
+ display_entry(textui, e)
+ textui.print_plain("")
+
+api.register(automount_findmap)
+
+
+class automount_findkey(crud.Find):
+ 'Search automount keys.'
+ takes_options = (
+ Flag('all?', doc='Retrieve all attributes'),
+ )
+ def get_args(self):
+ return (Str('automountkey',
+ cli_name='key',
+ doc='An entry in an automount map'),)
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ search_fields = key_attributes
+
+ for s in search_fields:
+ kw[s] = term
+
+ kw['objectclass'] = 'automount'
+ kw['base'] = api.env.container_automount
+ if kw.get('all', False):
+ kw['attributes'] = ['*']
+ else:
+ kw['attributes'] = key_attributes
+ return ldap.search(**kw)
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ entries = result[1:]
+ if counter == 0:
+ textui.print_plain("No entries found")
+ return
+ elif counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+
+ for e in entries:
+ display_entry(textui, e)
+ textui.print_plain("")
+
+api.register(automount_findkey)
+
+
+class automount_showmap(crud.Get):
+ 'Examine an existing automount map.'
+ takes_options = (
+ Flag('all?', doc='Retrieve all attributes'),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-showmap operation.
+
+ Returns the entry
+
+ :param mapname: The automount map to retrieve
+ :param kw: "all" set to True = return all attributes
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ # FIXME: should kw contain the list of attributes to display?
+ if kw.get('all', False):
+ return ldap.retrieve(dn)
+ else:
+ return ldap.retrieve(dn, map_attributes)
+ def output_for_cli(self, textui, result, *args, **options):
+ if result:
+ display_entry(textui, result)
+
+api.register(automount_showmap)
+
+
+class automount_showkey(crud.Get):
+ 'Examine an existing automount key.'
+ takes_options = (
+ Str('automountkey',
+ cli_name='key',
+ doc='The automount key to display'),
+ Flag('all?', doc='Retrieve all attributes'),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-showkey operation.
+
+ Returns the entry
+
+ :param mapname: The mapname to examine
+ :param kw: "automountkey" the key to retrieve
+ :param kw: "all" set to True = return all attributes
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ keys = api.Command['automount_getkeys'](mapname)
+ keyname = kw.get('automountkey').lower()
+ keydn = None
+ if keys:
+ for k in keys:
+ if k.get('automountkey').lower() == keyname:
+ keydn = k.get('dn')
+ break
+ if not keydn:
+ raise errors.NotFound
+ # FIXME: should kw contain the list of attributes to display?
+ if kw.get('all', False):
+ return ldap.retrieve(keydn)
+ else:
+ return ldap.retrieve(keydn, key_attributes)
+ def output_for_cli(self, textui, result, *args, **options):
+ # The automount map name associated with this key is available only
+ # in the dn. Add it as an attribute to display instead.
+ if result and not result.get('automountmapname'):
+ elements = explode_dn(result.get('dn').lower())
+ for e in elements:
+ (attr, value) = e.split('=',1)
+ if attr == 'automountmapname':
+ result['automountmapname'] = value
+ display_entry(textui, result)
+
+api.register(automount_showkey)
+
+
+class automount_getkeys(Command):
+ 'Retrieve all keys for an automount map.'
+ takes_args = (
+ Str('automountmapname',
+ cli_name='mapname',
+ primary_key=True,
+ doc='A group of related automount objects',
+ ),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-getkeys operation.
+
+ Return a list of all automount keys for this mapname
+
+ :param mapname: Retrieve all keys for this mapname
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("automountmapname", mapname, "automountmap", api.env.container_automount)
+ try:
+ keys = ldap.get_one_entry(dn, 'objectclass=*', ['automountkey'])
+ except errors.NotFound:
+ keys = []
+
+ return keys
+ def output_for_cli(self, textui, result, *args, **options):
+ for k in result:
+ textui.print_plain('%s' % k.get('automountkey'))
+
+api.register(automount_getkeys)
+
+
+class automount_getmaps(Command):
+ 'Retrieve all automount maps'
+ takes_args = (
+ Str('automountmapname?',
+ cli_name='mapname',
+ primary_key=True,
+ doc='A group of related automount objects',
+ ),
+ )
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-getmaps operation.
+
+ Return a list of all automount maps.
+ """
+
+ ldap = self.api.Backend.ldap
+ base = api.env.container_automount + "," + api.env.basedn
+
+ if not mapname:
+ mapname = "auto.master"
+ search_base = "automountmapname=%s,%s" % (mapname, base)
+ maps = ldap.get_one_entry(search_base, "objectClass=*", ["*"])
+
+ return maps
+ def output_for_cli(self, textui, result, *args, **options):
+ for k in result:
+ textui.print_plain('%s: %s' % (k.get('automountinformation'), k.get('automountkey')))
+
+api.register(automount_getmaps)
+
+class automount_addindirectmap(crud.Add):
+ """
+ Add a new automap indirect mount point.
+ """
+
+ takes_options = (
+ Str('parentmap?',
+ cli_name='parentmap',
+ default=u'auto.master',
+ doc='The parent map to connect this to.',
+ ),
+ Str('automountkey',
+ cli_name='key',
+ doc='An entry in an automount map',
+ ),
+ Str('description?',
+ doc='A description of the automount map',
+ ),
+ )
+
+ def execute(self, mapname, **kw):
+ """
+ Execute the automount-addindirectmap operation.
+
+ Returns the key entry as it will be created in LDAP.
+
+ This function creates 2 LDAP entries. It creates an
+ automountmapname entry and an automountkey entry.
+
+ :param mapname: The map name being added.
+ :param kw['parentmap'] is the top-level map to add this to.
+ defaulting to auto.master
+ :param kw['automountkey'] is the mount point
+ :param kw['description'] is a textual description of this map
+ """
+ mapkw = {}
+ if kw.get('description'):
+ mapkw['description'] = kw.get('description')
+ newmap = api.Command['automount_addmap'](mapname, **mapkw)
+
+ keykw = {'automountkey': kw['automountkey'], 'automountinformation': mapname}
+ if kw.get('description'):
+ keykw['description'] = kw.get('description')
+ newkey = api.Command['automount_addkey'](kw['parentmap'], **keykw)
+
+ return newkey
+ def output_for_cli(self, textui, result, map, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Indirect automount map %s added" % map)
+
+api.register(automount_addindirectmap)
diff --git a/ipalib/plugins/f_delegation.py b/ipalib/plugins/f_delegation.py
new file mode 100644
index 00000000..fbf8cfbf
--- /dev/null
+++ b/ipalib/plugins/f_delegation.py
@@ -0,0 +1,65 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for delegations.
+"""
+
+from ipalib import frontend
+from ipalib import crud
+from ipalib.frontend import Param
+from ipalib import api
+from ipalib import errors
+
+class delegation(frontend.Object):
+ """
+ Delegation object.
+ """
+ takes_params = (
+ 'attributes',
+ 'source',
+ 'target',
+ Param('name', primary_key=True)
+ )
+api.register(delegation)
+
+
+class delegation_add(crud.Add):
+ 'Add a new delegation.'
+api.register(delegation_add)
+
+
+class delegation_del(crud.Del):
+ 'Delete an existing delegation.'
+api.register(delegation_del)
+
+
+class delegation_mod(crud.Mod):
+ 'Edit an existing delegation.'
+api.register(delegation_mod)
+
+
+class delegation_find(crud.Find):
+ 'Search for a delegation.'
+api.register(delegation_find)
+
+
+class delegation_show(crud.Get):
+ 'Examine an existing delegation.'
+api.register(delegation_show)
diff --git a/ipalib/plugins/f_group.py b/ipalib/plugins/f_group.py
new file mode 100644
index 00000000..740b32f8
--- /dev/null
+++ b/ipalib/plugins/f_group.py
@@ -0,0 +1,384 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for group (Identity).
+"""
+
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str, Int # Parameter types
+
+
+def get_members(members):
+ """
+ Return a list of members.
+
+ It is possible that the value passed in is None.
+ """
+ if members:
+ members = members.split(',')
+ else:
+ members = []
+
+ return members
+
+class group(Object):
+ """
+ Group object.
+ """
+ takes_params = (
+ Str('description',
+ doc='A description of this group',
+ ),
+ Int('gidnumber?',
+ cli_name='gid',
+ doc='The gid to use for this group. If not included one is automatically set.',
+ ),
+ Str('cn',
+ cli_name='name',
+ primary_key=True,
+ normalizer=lambda value: value.lower(),
+ ),
+ )
+api.register(group)
+
+
+class group_add(crud.Add):
+ 'Add a new group.'
+
+ def execute(self, cn, **kw):
+ """
+ Execute the group-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry as it will be created in LDAP.
+
+ No need to explicitly set gidNumber. The dna_plugin will do this
+ for us if the value isn't provided by the caller.
+
+ :param cn: The name of the group being added.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ kw['cn'] = cn
+ kw['dn'] = ldap.make_group_dn(cn)
+
+ # Get our configuration
+ config = ldap.get_ipa_config()
+
+ # some required objectclasses
+ kw['objectClass'] = config.get('ipagroupobjectclasses')
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_name(self.name)
+ textui.print_entry(result)
+ textui.print_dashed('Added group "%s"' % result['cn'])
+
+api.register(group_add)
+
+
+class group_del(crud.Del):
+ 'Delete an existing group.'
+ def execute(self, cn, **kw):
+ """
+ Delete a group
+
+ The memberOf plugin handles removing the group from any other
+ groups.
+
+ :param cn: The name of the group being removed
+ :param kw: Unused
+ """
+ # We have 2 special groups, don't allow them to be removed
+# if "admins" == cn.lower() or "editors" == cn.lower():
+# raise ipaerror.gen_exception(ipaerror.CONFIG_REQUIRED_GROUPS)
+
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, "posixGroup")
+ self.log.info("IPA: group-del '%s'" % dn)
+
+ # Don't allow the default user group to be removed
+ config=ldap.get_ipa_config()
+ default_group = ldap.find_entry_dn("cn", config.get('ipadefaultprimarygroup'), "posixGroup")
+ if dn == default_group:
+ raise errors.DefaultGroup
+
+ return ldap.delete(dn)
+
+ def output_for_cli(self, textui, result, cn):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Deleted group %s" % cn)
+
+api.register(group_del)
+
+
+class group_mod(crud.Mod):
+ 'Edit an existing group.'
+ def execute(self, cn, **kw):
+ """
+ Execute the group-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The name of the group to update.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, "posixGroup")
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, cn, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ textui.print_plain("Group updated")
+
+api.register(group_mod)
+
+
+class group_find(crud.Find):
+ 'Search the groups.'
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ # Pull the list of searchable attributes out of the configuration.
+ config = ldap.get_ipa_config()
+ search_fields_conf_str = config.get('ipagroupsearchfields')
+ search_fields = search_fields_conf_str.split(",")
+
+ search_kw = {}
+ for s in search_fields:
+ search_kw[s] = term
+
+ object_type = ldap.get_object_type("cn")
+ if object_type and not kw.get('objectclass'):
+ search_kw['objectclass'] = object_type
+ return ldap.search(**search_kw)
+
+ def output_for_cli(self, textui, result, uid, **options):
+ counter = result[0]
+ groups = result[1:]
+ if counter == 0 or len(groups) == 0:
+ textui.print_plain("No entries found")
+ return
+ if len(groups) == 1:
+ textui.print_entry(groups[0])
+ return
+ textui.print_name(self.name)
+
+ for g in groups:
+ textui.print_entry(g)
+ textui.print_plain('')
+ if counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+ textui.print_count(groups, '%d groups matched')
+
+api.register(group_find)
+
+
+class group_show(crud.Get):
+ 'Examine an existing group.'
+ def execute(self, cn, **kw):
+ """
+ Execute the group-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The group name to retrieve.
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, "posixGroup")
+ # FIXME: should kw contain the list of attributes to display?
+ return ldap.retrieve(dn)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ groups = result[1:]
+ if counter == 0 or len(groups) == 0:
+ textui.print_plain("No entries found")
+ return
+ if len(groups) == 1:
+ textui.print_entry(groups[0])
+ return
+ textui.print_name(self.name)
+ for u in groups:
+ textui.print_plain('%(givenname)s %(sn)s:' % u)
+ textui.print_entry(u)
+ textui.print_plain('')
+ if counter == -1:
+ textui.print_plain('These results are truncated.')
+ textui.print_plain('Please refine your search and try again.')
+ textui.print_count(groups, '%d groups matched')
+
+api.register(group_show)
+
+
+class group_add_member(Command):
+ 'Add a member to a group.'
+ takes_args = (
+ Str('group', primary_key=True),
+ )
+ takes_options = (
+ Str('users?', doc='comma-separated list of users to add'),
+ Str('groups?', doc='comma-separated list of groups to add'),
+ )
+ def execute(self, cn, **kw):
+ """
+ Execute the group-add-member operation.
+
+ Returns the updated group entry
+
+ :param cn: The group name to add new members to.
+ :param kw: groups is a comma-separated list of groups to add
+ :parem kw: users is a comma-separated list of users to add
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn)
+ add_failed = []
+ to_add = []
+ completed = 0
+
+ members = get_members(kw.get('groups', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m)
+ to_add.append(member_dn)
+ except errors.NotFound:
+ add_failed.append(m)
+ continue
+
+ members = get_members(kw.get('users', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("uid", m)
+ to_add.append(member_dn)
+ except errors.NotFound:
+ add_failed.append(m)
+ continue
+
+ for member_dn in to_add:
+ try:
+ ldap.add_member_to_group(member_dn, dn)
+ completed+=1
+ except:
+ add_failed.append(member_dn)
+
+ return add_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ print "These entries failed to add to the group:"
+ for a in add_failed:
+ print "\t'%s'" % a
+
+
+api.register(group_add_member)
+
+
+class group_remove_member(Command):
+ 'Remove a member from a group.'
+ takes_args = (
+ Str('group', primary_key=True),
+ )
+ takes_options = (
+ Str('users?', doc='comma-separated list of users to remove'),
+ Str('groups?', doc='comma-separated list of groups to remove'),
+ )
+ def execute(self, cn, **kw):
+ """
+ Execute the group-remove-member operation.
+
+ Returns the members that could not be added
+
+ :param cn: The group name to add new members to.
+ :param kw: groups is a comma-separated list of groups to remove
+ :parem kw: users is a comma-separated list of users to remove
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn)
+ to_remove = []
+ remove_failed = []
+ completed = 0
+
+ members = get_members(kw.get('groups', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m)
+ to_remove.append(member_dn)
+ except errors.NotFound:
+ remove_failed.append(m)
+ continue
+
+ members = get_members(kw.get('users', ''))
+ for m in members:
+ try:
+ member_dn = ldap.find_entry_dn("uid", m,)
+ to_remove.append(member_dn)
+ except errors.NotFound:
+ remove_failed.append(m)
+ continue
+
+ for member_dn in to_remove:
+ try:
+ ldap.remove_member_from_group(member_dn, dn)
+ completed+=1
+ except:
+ remove_failed.append(member_dn)
+
+ return remove_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ print "These entries failed to be removed from the group:"
+ for a in remove_failed:
+ print "\t'%s'" % a
+
+api.register(group_remove_member)
diff --git a/ipalib/plugins/f_host.py b/ipalib/plugins/f_host.py
new file mode 100644
index 00000000..ea819a77
--- /dev/null
+++ b/ipalib/plugins/f_host.py
@@ -0,0 +1,286 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for host/machine Identity.
+"""
+
+from ipalib import api, crud, errors, util
+from ipalib import Object # Plugin base class
+from ipalib import Str, Flag # Parameter types
+
+
+def get_host(hostname):
+ """
+ Try to get the hostname as fully-qualified first, then fall back to
+ just a host name search.
+ """
+ ldap = api.Backend.ldap
+
+ # Strip off trailing dot
+ if hostname.endswith('.'):
+ hostname = hostname[:-1]
+ try:
+ dn = ldap.find_entry_dn("cn", hostname, "ipaHost")
+ except errors.NotFound:
+ dn = ldap.find_entry_dn("serverhostname", hostname, "ipaHost")
+ return dn
+
+def validate_host(ugettext, cn):
+ """
+ Require at least one dot in the hostname (to support localhost.localdomain)
+ """
+ dots = len(cn.split('.'))
+ if dots < 2:
+ return 'Fully-qualified hostname required'
+ return None
+
+default_attributes = ['cn','description','localityname','nshostlocation','nshardwareplatform','nsosversion']
+
+class host(Object):
+ """
+ Host object.
+ """
+ takes_params = (
+ Str('cn', validate_host,
+ cli_name='hostname',
+ primary_key=True,
+ normalizer=lambda value: value.lower(),
+ ),
+ Str('description?',
+ doc='Description of the host',
+ ),
+ Str('localityname?',
+ cli_name='locality',
+ doc='Locality of this host (Baltimore, MD)',
+ ),
+ Str('nshostlocation?',
+ cli_name='location',
+ doc='Location of this host (e.g. Lab 2)',
+ ),
+ Str('nshardwareplatform?',
+ cli_name='platform',
+ doc='Hardware platform of this host (e.g. Lenovo T61)',
+ ),
+ Str('nsosversion?',
+ cli_name='os',
+ doc='Operating System and version on this host (e.g. Fedora 9)',
+ ),
+ Str('userpassword?',
+ cli_name='password',
+ doc='Set a password to be used in bulk enrollment',
+ ),
+ )
+api.register(host)
+
+
+class host_add(crud.Add):
+ 'Add a new host.'
+ def execute(self, hostname, **kw):
+ """
+ Execute the host-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ If password is set then this is considered a 'bulk' host so we
+ do not create a kerberos service principal.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param hostname: The name of the host being added.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ assert 'krbprincipalname' not in kw
+ ldap = self.api.Backend.ldap
+
+ kw['cn'] = hostname
+ kw['serverhostname'] = hostname.split('.',1)[0]
+ kw['dn'] = ldap.make_host_dn(hostname)
+
+ # FIXME: do a DNS lookup to ensure host exists
+
+ current = util.get_current_principal()
+ if not current:
+ raise errors.NotFound('Unable to determine current user')
+ kw['enrolledby'] = ldap.find_entry_dn("krbPrincipalName", current, "posixAccount")
+
+ # Get our configuration
+ config = ldap.get_ipa_config()
+
+ # some required objectclasses
+ # FIXME: add this attribute to cn=ipaconfig
+ #kw['objectclass'] = config.get('ipahostobjectclasses')
+ kw['objectclass'] = ['nsHost', 'ipaHost', 'pkiUser']
+
+ # Ensure the list of objectclasses is lower-case
+ kw['objectclass'] = map(lambda z: z.lower(), kw.get('objectclass'))
+
+ if not kw.get('userpassword', False):
+ kw['krbprincipalname'] = "host/%s@%s" % (hostname, self.api.env.realm)
+
+ if 'krbprincipalaux' not in kw.get('objectclass'):
+ kw['objectclass'].append('krbprincipalaux')
+ else:
+ if 'krbprincipalaux' in kw.get('objectclass'):
+ kw['objectclass'].remove('krbprincipalaux')
+
+ return ldap.create(**kw)
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Host added")
+
+api.register(host_add)
+
+
+class host_del(crud.Del):
+ 'Delete an existing host.'
+ def execute(self, hostname, **kw):
+ """Delete a host.
+
+ hostname is the name of the host to delete
+
+ :param hostname: The name of the host being removed.
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ dn = get_host(hostname)
+ return ldap.delete(dn)
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Host deleted")
+
+api.register(host_del)
+
+
+class host_mod(crud.Mod):
+ 'Edit an existing host.'
+ def execute(self, hostname, **kw):
+ """
+ Execute the host-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param hostname: The name of the host to retrieve.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = get_host(hostname)
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Host updated")
+
+api.register(host_mod)
+
+
+class host_find(crud.Find):
+ 'Search the hosts.'
+
+ takes_options = (
+ Flag('all', doc='Retrieve all attributes'),
+ )
+
+ # FIXME: This should no longer be needed with the Param.query kwarg.
+# def get_args(self):
+# """
+# Override Find.get_args() so we can exclude the validation rules
+# """
+# yield self.obj.primary_key.__clone__(rules=tuple())
+
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ # Pull the list of searchable attributes out of the configuration.
+ #config = ldap.get_ipa_config()
+ # FIXME: add this attribute to cn=ipaconfig
+ #search_fields_conf_str = config.get('ipahostsearchfields')
+ #search_fields = search_fields_conf_str.split(",")
+ search_fields = ['cn','serverhostname','description','localityname','nshostlocation','nshardwareplatform','nsosversion']
+
+ search_kw = {}
+ for s in search_fields:
+ search_kw[s] = term
+
+ # Can't use ldap.get_object_type() since cn is also used for group dns
+ search_kw['objectclass'] = "ipaHost"
+ if kw.get('all', False):
+ search_kw['attributes'] = ['*']
+ else:
+ search_kw['attributes'] = default_attributes
+ return ldap.search(**search_kw)
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ hosts = result[1:]
+ if counter == 0:
+ textui.print_plain("No entries found")
+ return
+
+ for h in hosts:
+ textui.print_entry(h)
+ if counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+api.register(host_find)
+
+
+class host_show(crud.Get):
+ 'Examine an existing host.'
+ takes_options = (
+ Flag('all', doc='Display all host attributes'),
+ )
+ def execute(self, hostname, **kw):
+ """
+ Execute the host-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param hostname: The login name of the host to retrieve.
+ :param kw: "all" set to True = return all attributes
+ """
+ ldap = self.api.Backend.ldap
+ dn = get_host(hostname)
+ # FIXME: should kw contain the list of attributes to display?
+ if kw.get('all', False):
+ return ldap.retrieve(dn)
+ else:
+ value = ldap.retrieve(dn, default_attributes)
+ del value['dn']
+ return value
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_entry(result)
+
+api.register(host_show)
diff --git a/ipalib/plugins/f_hostgroup.py b/ipalib/plugins/f_hostgroup.py
new file mode 100644
index 00000000..706712c9
--- /dev/null
+++ b/ipalib/plugins/f_hostgroup.py
@@ -0,0 +1,354 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for groups of hosts
+"""
+
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str # Parameter types
+
+
+hostgroup_filter = "groupofnames)(!(objectclass=posixGroup)"
+
+def get_members(members):
+ """
+ Return a list of members.
+
+ It is possible that the value passed in is None.
+ """
+ if members:
+ members = members.split(',')
+ else:
+ members = []
+
+ return members
+
+class hostgroup(Object):
+ """
+ Host Group object.
+ """
+ takes_params = (
+ Str('description',
+ doc='A description of this group',
+ ),
+ Str('cn',
+ cli_name='name',
+ primary_key=True,
+ normalizer=lambda value: value.lower(),
+ )
+ )
+api.register(hostgroup)
+
+
+class hostgroup_add(crud.Add):
+ 'Add a new group of hosts.'
+
+ def execute(self, cn, **kw):
+ """
+ Execute the hostgroup-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry as it will be created in LDAP.
+
+ No need to explicitly set gidNumber. The dna_plugin will do this
+ for us if the value isn't provided by the caller.
+
+ :param cn: The name of the host group being added.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ kw['cn'] = cn
+ kw['dn'] = ldap.make_hostgroup_dn(cn)
+
+ # Get our configuration
+ #config = ldap.get_ipa_config()
+
+ # some required objectclasses
+ # FIXME: get this out of config
+ kw['objectClass'] = ['groupofnames']
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Group added")
+
+api.register(hostgroup_add)
+
+
+class hostgroup_del(crud.Del):
+ 'Delete an existing group of hosts.'
+ def execute(self, cn, **kw):
+ """
+ Delete a group of hosts
+
+ The memberOf plugin handles removing the group from any other
+ groups.
+
+ :param cn: The name of the group being removed
+ :param kw: Unused
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, hostgroup_filter)
+
+ return ldap.delete(dn)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain("Group deleted")
+
+api.register(hostgroup_del)
+
+
+class hostgroup_mod(crud.Mod):
+ 'Edit an existing group of hosts.'
+ def execute(self, cn, **kw):
+ """
+ Execute the hostgroup-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The name of the group to update.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, hostgroup_filter)
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ texui.print_plain("Group updated")
+
+api.register(hostgroup_mod)
+
+
+class hostgroup_find(crud.Find):
+ 'Search the groups of hosts.'
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ # Pull the list of searchable attributes out of the configuration.
+ config = ldap.get_ipa_config()
+
+ # FIXME: for now use same search fields as user groups
+ search_fields_conf_str = config.get('ipagroupsearchfields')
+ search_fields = search_fields_conf_str.split(",")
+
+ search_kw = {}
+ for s in search_fields:
+ search_kw[s] = term
+
+ search_kw['objectclass'] = hostgroup_filter
+ return ldap.search(**search_kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ groups = result[1:]
+ if counter == 0:
+ textui.print_plain("No entries found")
+ return
+
+ for g in groups:
+ textui.print_entry(g)
+
+ if counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+
+api.register(hostgroup_find)
+
+
+class hostgroup_show(crud.Get):
+ 'Examine an existing group of hosts.'
+ def execute(self, cn, **kw):
+ """
+ Execute the hostgroup-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The group name to retrieve.
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ # FIXME: this works for now but the plan is to add a new objectclass
+ # type.
+ dn = ldap.find_entry_dn("cn", cn, hostgroup_filter)
+ # FIXME: should kw contain the list of attributes to display?
+ return ldap.retrieve(dn)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_entry(result)
+
+api.register(hostgroup_show)
+
+
+class hostgroup_add_member(Command):
+ 'Add a member to a group.'
+ takes_args = (
+ Str('group', primary_key=True),
+ )
+ takes_options = (
+ Str('groups?', doc='comma-separated list of host groups to add'),
+ Str('hosts?', doc='comma-separated list of hosts to add'),
+ )
+ def execute(self, cn, **kw):
+ """
+ Execute the hostgroup-add-member operation.
+
+ Returns the updated group entry
+
+ :param cn: The group name to add new members to.
+ :param kw: groups is a comma-separated list of host groups to add
+ :param kw: hosts is a comma-separated list of hosts to add
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, hostgroup_filter)
+ add_failed = []
+ to_add = []
+ completed = 0
+
+ members = get_members(kw.get('groups', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m, hostgroup_filter)
+ to_add.append(member_dn)
+ except errors.NotFound:
+ add_failed.append(m)
+ continue
+
+ members = get_members(kw.get('hosts', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m, "ipaHost")
+ to_add.append(member_dn)
+ except errors.NotFound:
+ add_failed.append(m)
+ continue
+
+ for member_dn in to_add:
+ try:
+ ldap.add_member_to_group(member_dn, dn)
+ completed+=1
+ except:
+ add_failed.append(member_dn)
+
+ return add_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ textui.print_plain("These entries failed to add to the group:")
+ for a in result:
+ print "\t'%s'" % a
+ else:
+ textui.print_plain("Group membership updated.")
+
+api.register(hostgroup_add_member)
+
+
+class hostgroup_remove_member(Command):
+ 'Remove a member from a group.'
+ takes_args = (
+ Str('group', primary_key=True),
+ )
+ takes_options = (
+ Str('hosts?', doc='comma-separated list of hosts to add'),
+ Str('groups?', doc='comma-separated list of groups to remove'),
+ )
+ def execute(self, cn, **kw):
+ """
+ Execute the group-remove-member operation.
+
+ Returns the members that could not be added
+
+ :param cn: The group name to add new members to.
+ :param kw: groups is a comma-separated list of groups to remove
+ :param kw: hosts is a comma-separated list of hosts to add
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, hostgroup_filter)
+ to_remove = []
+ remove_failed = []
+ completed = 0
+
+ members = get_members(kw.get('groups', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m, hostgroup_filter)
+ to_remove.append(member_dn)
+ except errors.NotFound:
+ remove_failed.append(m)
+ continue
+
+ members = get_members(kw.get('hosts', ''))
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn("cn", m, "ipaHost")
+ to_remove.append(member_dn)
+ except errors.NotFound:
+ remove_failed.append(m)
+ continue
+
+ for member_dn in to_remove:
+ try:
+ ldap.remove_member_from_group(member_dn, dn)
+ completed+=1
+ except:
+ remove_failed.append(member_dn)
+
+ return remove_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ textui.print_plain("These entries failed to be removed from the group:")
+ for a in result:
+ print "\t'%s'" % a
+ else:
+ textui.print_plain("Group membership updated.")
+
+api.register(hostgroup_remove_member)
diff --git a/ipalib/plugins/f_misc.py b/ipalib/plugins/f_misc.py
new file mode 100644
index 00000000..a2f0fa4e
--- /dev/null
+++ b/ipalib/plugins/f_misc.py
@@ -0,0 +1,89 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Misc frontend plugins.
+"""
+
+import re
+from ipalib import api, LocalOrRemote
+
+
+
+# FIXME: We should not let env return anything in_server
+# when mode == 'production'. This would allow an attacker to see the
+# configuration of the server, potentially revealing compromising
+# information. However, it's damn handy for testing/debugging.
+class env(LocalOrRemote):
+ """Show environment variables"""
+
+ takes_args = ('variables*',)
+
+ def __find_keys(self, variables):
+ keys = set()
+ for query in variables:
+ if '*' in query:
+ pat = re.compile(query.replace('*', '.*') + '$')
+ for key in self.env:
+ if pat.match(key):
+ keys.add(key)
+ elif query in self.env:
+ keys.add(query)
+ return sorted(keys)
+
+ def execute(self, variables, **options):
+ if variables is None:
+ keys = self.env
+ else:
+ keys = self.__find_keys(variables)
+ return tuple(
+ (key, self.env[key]) for key in keys
+ )
+
+ def output_for_cli(self, textui, result, variables, **options):
+ if len(result) == 0:
+ return
+ if len(result) == 1:
+ textui.print_keyval(result)
+ return
+ textui.print_name(self.name)
+ textui.print_keyval(result)
+ textui.print_count(result, '%d variables')
+
+api.register(env)
+
+
+class plugins(LocalOrRemote):
+ """Show all loaded plugins"""
+
+ def execute(self, **options):
+ plugins = sorted(self.api.plugins, key=lambda o: o.plugin)
+ return tuple(
+ (p.plugin, p.bases) for p in plugins
+ )
+
+ def output_for_cli(self, textui, result, **options):
+ textui.print_name(self.name)
+ for (plugin, bases) in result:
+ textui.print_indented(
+ '%s: %s' % (plugin, ', '.join(bases))
+ )
+ textui.print_count(result, '%d plugin loaded', '%s plugins loaded')
+
+api.register(plugins)
diff --git a/ipalib/plugins/f_netgroup.py b/ipalib/plugins/f_netgroup.py
new file mode 100644
index 00000000..6ee55b0d
--- /dev/null
+++ b/ipalib/plugins/f_netgroup.py
@@ -0,0 +1,461 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugin for netgroups.
+"""
+
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str # Parameter types
+from ipalib import uuid
+
+netgroup_base = "cn=ng, cn=alt"
+netgroup_filter = "ipaNISNetgroup"
+hostgroup_filter = "groupofnames)(!(objectclass=posixGroup)"
+
+def get_members(members):
+ """
+ Return a list of members.
+
+ It is possible that the value passed in is None.
+ """
+ if members:
+ members = members.split(',')
+ else:
+ members = []
+
+ return members
+
+def find_members(ldap, failed, members, attribute, filter=None):
+ """
+ Return 2 lists: one a list of DNs found, one a list of errors
+ """
+ found = []
+ for m in members:
+ if not m: continue
+ try:
+ member_dn = ldap.find_entry_dn(attribute, m, filter)
+ found.append(member_dn)
+ except errors.NotFound:
+ failed.append(m)
+ continue
+
+ return found, failed
+
+def add_members(ldap, completed, members, dn, memberattr):
+ add_failed = []
+ for member_dn in members:
+ try:
+ ldap.add_member_to_group(member_dn, dn, memberattr)
+ completed+=1
+ except:
+ add_failed.append(member_dn)
+
+ return completed, add_failed
+
+def add_external(ldap, completed, members, cn):
+ failed = []
+ netgroup = api.Command['netgroup_show'](cn)
+ external = netgroup.get('externalhost', [])
+ if not isinstance(external, list):
+ external = [external]
+ external_len = len(external)
+ for m in members:
+ if not m in external:
+ external.append(m)
+ completed+=1
+ else:
+ failed.append(m)
+ if len(external) > external_len:
+ kw = {'externalhost': external}
+ ldap.update(netgroup['dn'], **kw)
+
+ return completed, failed
+
+def remove_members(ldap, completed, members, dn, memberattr):
+ remove_failed = []
+ for member_dn in members:
+ try:
+ ldap.remove_member_from_group(member_dn, dn, memberattr)
+ completed+=1
+ except:
+ remove_failed.append(member_dn)
+
+ return completed, remove_failed
+
+def remove_external(ldap, completed, members, cn):
+ failed = []
+ netgroup = api.Command['netgroup_show'](cn)
+ external = netgroup.get('externalhost', [])
+ if not isinstance(external, list):
+ external = [external]
+ external_len = len(external)
+ for m in members:
+ try:
+ external.remove(m)
+ completed+=1
+ except ValueError:
+ failed.append(m)
+ if len(external) < external_len:
+ kw = {'externalhost': external}
+ ldap.update(netgroup['dn'], **kw)
+
+ return completed, failed
+
+class netgroup(Object):
+ """
+ netgroups object.
+ """
+ takes_params = (
+ Str('cn',
+ cli_name='name',
+ primary_key=True
+ ),
+ Str('description',
+ doc='Description',
+ ),
+ Str('nisdomainname?',
+ cli_name='domainname',
+ doc='Domain name',
+ ),
+ )
+api.register(netgroup)
+
+
+class netgroup_add(crud.Add):
+ 'Add a new netgroup.'
+
+ def execute(self, cn, **kw):
+ """
+ Execute the netgroup-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param cn: The name of the netgroup
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ self.log.info("IPA: netgroup-add '%s'" % cn)
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ kw['cn'] = cn
+# kw['dn'] = ldap.make_netgroup_dn()
+ kw['ipauniqueid'] = str(uuid.uuid1())
+ kw['dn'] = "ipauniqueid=%s,%s,%s" % (kw['ipauniqueid'], netgroup_base, api.env.basedn)
+
+ if not kw.get('nisdomainname', False):
+ kw['nisdomainname'] = api.env.domain
+
+ # some required objectclasses
+ kw['objectClass'] = ['top', 'ipaAssociation', 'ipaNISNetgroup']
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_name(self.name)
+ textui.print_entry(result)
+ textui.print_dashed('Added netgroup "%s"' % result.get('cn'))
+
+api.register(netgroup_add)
+
+
+class netgroup_del(crud.Del):
+ 'Delete an existing netgroup.'
+
+ def execute(self, cn, **kw):
+ """Delete a netgroup.
+
+ cn is the cn of the netgroup to delete
+
+ The memberOf plugin handles removing the netgroup from any other
+ groups.
+
+ :param cn: The name of the netgroup being removed.
+ :param kw: Not used.
+ """
+ self.log.info("IPA: netgroup-del '%s'" % cn)
+
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, netgroup_filter, netgroup_base)
+ return ldap.delete(dn)
+
+ def output_for_cli(self, textui, result, cn):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain('Deleted net group "%s"' % cn)
+
+api.register(netgroup_del)
+
+
+class netgroup_mod(crud.Mod):
+ 'Edit an existing netgroup.'
+ def execute(self, cn, **kw):
+ """
+ Execute the netgroup-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The name of the netgroup to retrieve.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ self.log.info("IPA: netgroup-mod '%s'" % cn)
+ assert 'cn' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, netgroup_filter, netgroup_base)
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, cn, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_name(self.name)
+ textui.print_entry(result)
+ textui.print_dashed('Updated netgroup "%s"' % result['cn'])
+
+api.register(netgroup_mod)
+
+
+class netgroup_find(crud.Find):
+ 'Search the netgroups.'
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ search_fields = ['ipauniqueid','description','nisdomainname','cn']
+
+ search_kw = {}
+ for s in search_fields:
+ search_kw[s] = term
+
+ search_kw['objectclass'] = netgroup_filter
+ search_kw['base'] = netgroup_base
+ return ldap.search(**search_kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ groups = result[1:]
+ if counter == 0 or len(groups) == 0:
+ textui.print_plain("No entries found")
+ return
+ if len(groups) == 1:
+ textui.print_entry(groups[0])
+ return
+ textui.print_name(self.name)
+ for g in groups:
+ textui.print_entry(g)
+ textui.print_plain('')
+ if counter == -1:
+ textui.print_plain('These results are truncated.')
+ textui.print_plain('Please refine your search and try again.')
+ textui.print_count(groups, '%d netgroups matched')
+
+api.register(netgroup_find)
+
+
+class netgroup_show(crud.Get):
+ 'Examine an existing netgroup.'
+ def execute(self, cn, **kw):
+ """
+ Execute the netgroup-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param cn: The name of the netgroup to retrieve.
+ :param kw: Unused
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, netgroup_filter, netgroup_base)
+ return ldap.retrieve(dn)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_entry(result)
+
+api.register(netgroup_show)
+
+class netgroup_add_member(Command):
+ 'Add a member to a group.'
+ takes_args = (
+ Str('cn',
+ cli_name='name',
+ primary_key=True
+ ),
+ )
+ takes_options = (
+ Str('hosts?', doc='comma-separated list of hosts to add'),
+ Str('hostgroups?', doc='comma-separated list of host groups to add'),
+ Str('users?', doc='comma-separated list of users to add'),
+ Str('groups?', doc='comma-separated list of groups to add'),
+ )
+
+ def execute(self, cn, **kw):
+ """
+ Execute the netgroup-add-member operation.
+
+ Returns the updated group entry
+
+ :param cn: The netgroup name to add new members to.
+ :param kw: hosts is a comma-separated list of hosts to add
+ :param kw: hostgroups is a comma-separated list of host groups to add
+ :param kw: users is a comma-separated list of users to add
+ :param kw: groups is a comma-separated list of host to add
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, netgroup_filter, netgroup_base)
+ add_failed = []
+ to_add = []
+ completed = 0
+
+ # Hosts
+ members = get_members(kw.get('hosts', ''))
+ (to_add, add_failed) = find_members(ldap, add_failed, members, "cn", "ipaHost")
+
+ # If a host is not found we'll consider it an externalHost. It will
+ # be up to the user to handle typos
+ if add_failed:
+ (completed, failed) = add_external(ldap, completed, add_failed, cn)
+ add_failed = failed
+
+ (completed, failed) = add_members(ldap, completed, to_add, dn, 'memberhost')
+ add_failed+=failed
+
+ # Host groups
+ members = get_members(kw.get('hostgroups', ''))
+ (to_add, add_failed) = find_members(ldap, add_failed, members, "cn", hostgroup_filter)
+ (completed, failed) = add_members(ldap, completed, to_add, dn, 'memberhost')
+ add_failed+=failed
+
+ # User
+ members = get_members(kw.get('users', ''))
+ (to_add, add_failed) = find_members(ldap, add_failed, members, "uid")
+ (completed, failed) = add_members(ldap, completed, to_add, dn, 'memberuser')
+ add_failed+=failed
+
+ # Groups
+ members = get_members(kw.get('groups', ''))
+ (to_add, add_failed) = find_members(ldap, add_failed, members, "cn", "posixGroup")
+ (completed, failed) = add_members(ldap, completed, to_add, dn, 'memberuser')
+ add_failed+=failed
+
+ return add_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ textui.print_plain("These entries failed to add to the group:")
+ for a in result:
+ print "\t'%s'" % a
+ else:
+ textui.print_plain("netgroup membership updated.")
+
+api.register(netgroup_add_member)
+
+
+class netgroup_remove_member(Command):
+ 'Remove a member from a group.'
+ takes_args = (
+ Str('cn',
+ cli_name='name',
+ primary_key=True
+ ),
+ )
+ takes_options = (
+ Str('hosts?', doc='comma-separated list of hosts to remove'),
+ Str('hostgroups?', doc='comma-separated list of groups to remove'),
+ Str('users?', doc='comma-separated list of users to remove'),
+ Str('groups?', doc='comma-separated list of groups to remove'),
+ )
+ def execute(self, cn, **kw):
+ """
+ Execute the group-remove-member operation.
+
+ Returns the members that could not be added
+
+ :param cn: The group name to add new members to.
+ :param kw: hosts is a comma-separated list of hosts to remove
+ :param kw: hostgroups is a comma-separated list of host groups to remove
+ :param kw: users is a comma-separated list of users to remove
+ :param kw: groups is a comma-separated list of host to remove
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", cn, netgroup_filter, netgroup_base)
+ remove_failed = []
+ to_remove = []
+ completed = 0
+
+ # Hosts
+ members = get_members(kw.get('hosts', ''))
+ (to_remove, remove_failed) = find_members(ldap, remove_failed, members, "cn", "ipaHost")
+
+ # If a host is not found we'll consider it an externalHost. It will
+ # be up to the user to handle typos
+ if remove_failed:
+ (completed, failed) = remove_external(ldap, completed, remove_failed, cn)
+ remove_failed = failed
+
+ (completed, failed) = remove_members(ldap, completed, to_remove, dn, 'memberhost')
+ remove_failed+=failed
+
+ # Host groups
+ members = get_members(kw.get('hostgroups', ''))
+ (to_remove, remove_failed) = find_members(ldap, remove_failed, members, "cn", hostgroup_filter)
+ (completed, failed) = remove_members(ldap, completed, to_remove, dn, 'memberhost')
+ remove_failed+=failed
+
+ # User
+ members = get_members(kw.get('users', ''))
+ (to_remove, remove_failed) = find_members(ldap, remove_failed, members, "uid")
+ (completed, failed) = remove_members(ldap, completed, to_remove, dn, 'memberuser')
+ remove_failed+=failed
+
+ # Groups
+ members = get_members(kw.get('groups', ''))
+ (to_remove, remove_failed) = find_members(ldap, remove_failed, members, "cn", "posixGroup")
+ (completed, failed) = remove_members(ldap, completed, to_remove, dn, 'memberuser')
+ remove_failed+=failed
+
+ return remove_failed
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ if result:
+ textui.print_plain("These entries failed to be removed from the group:")
+ for a in result:
+ print "\t'%s'" % a
+ else:
+ textui.print_plain("netgroup membership updated.")
+
+api.register(netgroup_remove_member)
diff --git a/ipalib/plugins/f_passwd.py b/ipalib/plugins/f_passwd.py
new file mode 100644
index 00000000..ea78c4c1
--- /dev/null
+++ b/ipalib/plugins/f_passwd.py
@@ -0,0 +1,70 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for password changes.
+"""
+
+from ipalib import api, errors, util
+from ipalib import Command # Plugin base classes
+from ipalib import Str, Password # Parameter types
+
+
+class passwd(Command):
+ 'Edit existing password policy.'
+
+ takes_args = (
+ Str('principal',
+ cli_name='user',
+ primary_key=True,
+ default_from=util.get_current_principal,
+ ),
+ Password('password'),
+ )
+
+ def execute(self, principal, password):
+ """
+ Execute the passwd operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param param uid: The login name of the user being updated.
+ :param kw: Not used.
+ """
+ if principal.find('@') > 0:
+ u = principal.split('@')
+ if len(u) > 2:
+ raise errors.InvalidUserPrincipal, principal
+ else:
+ principal = principal+"@"+self.api.env.realm
+ dn = self.Backend.ldap.find_entry_dn(
+ "krbprincipalname",
+ principal,
+ "posixAccount"
+ )
+ return self.Backend.ldap.modify_password(dn, newpass=password)
+
+ def output_for_cli(self, textui, result, principal, password):
+ assert password is None
+ textui.print_plain('Changed password for "%s"' % principal)
+
+api.register(passwd)
diff --git a/ipalib/plugins/f_pwpolicy.py b/ipalib/plugins/f_pwpolicy.py
new file mode 100644
index 00000000..d914ce72
--- /dev/null
+++ b/ipalib/plugins/f_pwpolicy.py
@@ -0,0 +1,122 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for password policy.
+"""
+
+from ipalib import api
+from ipalib import Command # Plugin base classes
+from ipalib import Int # Parameter types
+
+
+class pwpolicy_mod(Command):
+ 'Edit existing password policy.'
+ takes_options = (
+ Int('krbmaxpwdlife?',
+ cli_name='maxlife',
+ doc='Max. Password Lifetime (days)'
+ ),
+ Int('krbminpwdlife?',
+ cli_name='minlife',
+ doc='Min. Password Lifetime (hours)'
+ ),
+ Int('krbpwdhistorylength?',
+ cli_name='history',
+ doc='Password History Size'
+ ),
+ Int('krbpwdmindiffchars?',
+ cli_name='minclasses',
+ doc='Min. Number of Character Classes'
+ ),
+ Int('krbpwdminlength?',
+ cli_name='minlength',
+ doc='Min. Length of Password'
+ ),
+ )
+ def execute(self, *args, **kw):
+ """
+ Execute the pwpolicy-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param args: This function takes no positional arguments
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", "accounts", "krbPwdPolicy")
+
+ # The LDAP routines want strings, not ints, so convert a few
+ # things. Otherwise it sees a string -> int conversion as a change.
+ for k in kw.iterkeys():
+ if k.startswith("krb", 0, 3):
+ kw[k] = str(kw[k])
+
+ # Convert hours and days to seconds
+ if kw.get('krbmaxpwdlife'):
+ kw['krbmaxpwdlife'] = str(int(kw.get('krbmaxpwdlife')) * 86400)
+ if kw.get('krbminpwdlife'):
+ kw['krbminpwdlife'] = str(int(kw.get('krbminpwdlife')) * 3600)
+
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_plain("Policy modified")
+
+api.register(pwpolicy_mod)
+
+
+class pwpolicy_show(Command):
+ 'Retrieve current password policy'
+ def execute(self, *args, **kw):
+ """
+ Execute the pwpolicy-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param args: Not used.
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("cn", "accounts", "krbPwdPolicy")
+
+ policy = ldap.retrieve(dn)
+
+ # convert some values for display purposes
+ policy['krbmaxpwdlife'] = str(int(policy.get('krbmaxpwdlife')) / 86400)
+ policy['krbminpwdlife'] = str(int(policy.get('krbminpwdlife')) / 3600)
+
+ return policy
+
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_plain("Password Policy")
+ textui.print_plain("Min. Password Lifetime (hours): %s" % result.get('krbminpwdlife'))
+ textui.print_plain("Max. Password Lifetime (days): %s" % result.get('krbmaxpwdlife'))
+ textui.print_plain("Min. Number of Character Classes: %s" % result.get('krbpwdmindiffchars'))
+ textui.print_plain("Min. Length of Password: %s" % result.get('krbpwdminlength'))
+ textui.print_plain("Password History Size: %s" % result.get('krbpwdhistorylength'))
+
+api.register(pwpolicy_show)
diff --git a/ipalib/plugins/f_ra.py b/ipalib/plugins/f_ra.py
new file mode 100644
index 00000000..7ac84e65
--- /dev/null
+++ b/ipalib/plugins/f_ra.py
@@ -0,0 +1,117 @@
+# Authors:
+# Andrew Wnuk <awnuk@redhat.com>
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for IPA-RA PKI operations.
+"""
+
+from ipalib import api, Command, Str, Int
+
+
+class request_certificate(Command):
+ """ Submit a certificate request. """
+
+ takes_args = ['csr']
+
+ takes_options = [Str('request_type?', default=u'pkcs10')]
+
+ def execute(self, csr, **options):
+ return self.Backend.ra.request_certificate(csr, **options)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ if isinstance(result, dict) and len(result) > 0:
+ textui.print_entry(result, 0)
+ else:
+ textui.print_plain('Failed to submit a certificate request.')
+
+api.register(request_certificate)
+
+
+class get_certificate(Command):
+ """ Retrieve an existing certificate. """
+
+ takes_args = ['serial_number']
+
+ def execute(self, serial_number, **options):
+ return self.Backend.ra.get_certificate(serial_number)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ if isinstance(result, dict) and len(result) > 0:
+ textui.print_entry(result, 0)
+ else:
+ textui.print_plain('Failed to obtain a certificate.')
+
+api.register(get_certificate)
+
+
+class check_request_status(Command):
+ """ Check a request status. """
+
+ takes_args = ['request_id']
+
+
+ def execute(self, request_id, **options):
+ return self.Backend.ra.check_request_status(request_id)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ if isinstance(result, dict) and len(result) > 0:
+ textui.print_entry(result, 0)
+ else:
+ textui.print_plain('Failed to retrieve a request status.')
+
+api.register(check_request_status)
+
+
+class revoke_certificate(Command):
+ """ Revoke a certificate. """
+
+ takes_args = ['serial_number']
+
+ # FIXME: The default is 0. Is this really an Int param?
+ takes_options = [Int('revocation_reason?', default=0)]
+
+
+ def execute(self, serial_number, **options):
+ return self.Backend.ra.revoke_certificate(serial_number, **options)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ if isinstance(result, dict) and len(result) > 0:
+ textui.print_entry(result, 0)
+ else:
+ textui.print_plain('Failed to revoke a certificate.')
+
+api.register(revoke_certificate)
+
+
+class take_certificate_off_hold(Command):
+ """ Take a revoked certificate off hold. """
+
+ takes_args = ['serial_number']
+
+ def execute(self, serial_number, **options):
+ return self.Backend.ra.take_certificate_off_hold(serial_number)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ if isinstance(result, dict) and len(result) > 0:
+ textui.print_entry(result, 0)
+ else:
+ textui.print_plain('Failed to take a revoked certificate off hold.')
+
+api.register(take_certificate_off_hold)
diff --git a/ipalib/plugins/f_service.py b/ipalib/plugins/f_service.py
new file mode 100644
index 00000000..06d6a5d0
--- /dev/null
+++ b/ipalib/plugins/f_service.py
@@ -0,0 +1,204 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+# Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for service (Identity).
+"""
+
+from ipalib import api, crud, errors
+from ipalib import Object # Plugin base classes
+from ipalib import Str, Flag # Parameter types
+
+
+class service(Object):
+ """
+ Service object.
+ """
+ takes_params = (
+ Str('principal', primary_key=True),
+ )
+api.register(service)
+
+
+class service_add(crud.Add):
+ """
+ Add a new service.
+ """
+
+ takes_options = (
+ Flag('force',
+ doc='Force a service principal name',
+ ),
+ )
+ def execute(self, principal, **kw):
+ """
+ Execute the service-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param principal: The service to be added in the form: service/hostname
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'krbprincipalname' not in kw
+ ldap = self.api.Backend.ldap
+
+ force = kw.get('force', False)
+ try:
+ del kw['force']
+ except:
+ pass
+
+ # Break down the principal into its component parts, which may or
+ # may not include the realm.
+ sp = principal.split('/')
+ if len(sp) != 2:
+ raise errors.MalformedServicePrincipal
+ service = sp[0]
+
+ if service.lower() == "host":
+ raise errors.HostService
+
+ sr = sp[1].split('@')
+ if len(sr) == 1:
+ hostname = sr[0].lower()
+ realm = self.api.env.realm
+ elif len(sr) == 2:
+ hostname = sr[0].lower()
+ realm = sr[1]
+ else:
+ raise MalformedServicePrincipal
+
+ """
+ FIXME once DNS client is done
+ if not force:
+ fqdn = hostname + "."
+ rs = dnsclient.query(fqdn, dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
+ if len(rs) == 0:
+ self.log.debug("IPA: DNS A record lookup failed for '%s'" % hostname)
+ raise ipaerror.gen_exception(ipaerror.INPUT_NOT_DNS_A_RECORD)
+ else:
+ self.log.debug("IPA: found %d records for '%s'" % (len(rs), hostname))
+ """
+
+ # At some point we'll support multiple realms
+ if (realm != self.api.env.realm):
+ raise errors.RealmMismatch
+
+ # Put the principal back together again
+ princ_name = service + "/" + hostname + "@" + realm
+
+ dn = ldap.make_service_dn(princ_name)
+
+ kw['dn'] = dn
+ kw['objectClass'] = ['krbPrincipal', 'krbPrincipalAux', 'krbTicketPolicyAux']
+
+ return ldap.create(**kw)
+
+ def output_to_cli(self, ret):
+ if ret:
+ print "Service added"
+
+api.register(service_add)
+
+
+class service_del(crud.Del):
+ 'Delete an existing service.'
+ def execute(self, principal, **kw):
+ """
+ Delete a service principal.
+
+ principal is the krbprincipalname of the entry to delete.
+
+ This should be called with much care.
+
+ :param principal: The service to be added in the form: service/hostname
+ :param kw: not used
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("krbprincipalname", principal)
+ return ldap.delete(dn)
+
+ def output_to_cli(self, ret):
+ if ret:
+ print "Service removed"
+
+api.register(service_del)
+
+# There is no service-mod. The principal itself contains nothing that
+# is user-changeable
+
+class service_find(crud.Find):
+ 'Search the existing services.'
+ def execute(self, principal, **kw):
+ ldap = self.api.Backend.ldap
+
+ search_kw = {}
+ search_kw['filter'] = "&(objectclass=krbPrincipalAux)(!(objectClass=posixAccount))(!(|(krbprincipalname=kadmin/*)(krbprincipalname=K/M@*)(krbprincipalname=krbtgt/*)))"
+ search_kw['krbprincipalname'] = principal
+
+ object_type = ldap.get_object_type("krbprincipalname")
+ if object_type and not kw.get('objectclass'):
+ search_kw['objectclass'] = object_type
+
+ return ldap.search(**search_kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ counter = result[0]
+ services = result[1:]
+ if counter == 0:
+ textui.print_plain("No entries found")
+ return
+
+ for s in services:
+ textui.print_entry(s)
+
+ if counter == -1:
+ textui.print_plain("These results are truncated.")
+ textui.print_plain("Please refine your search and try again.")
+ textui.print_count(services, '%d services matched')
+
+api.register(service_find)
+
+
+class service_show(crud.Get):
+ 'Examine an existing service.'
+ def execute(self, principal, **kw):
+ """
+ Execute the service-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param principal: The service principal to retrieve
+ :param kw: Not used.
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("krbprincipalname", principal)
+ # FIXME: should kw contain the list of attributes to display?
+ return ldap.retrieve(dn)
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_entry(result)
+
+api.register(service_show)
diff --git a/ipalib/plugins/f_user.py b/ipalib/plugins/f_user.py
new file mode 100644
index 00000000..506ad14d
--- /dev/null
+++ b/ipalib/plugins/f_user.py
@@ -0,0 +1,367 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Frontend plugins for user (Identity).
+"""
+
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str, Password, Flag, Int # Parameter types
+
+
+def display_user(user):
+ # FIXME: for now delete dn here. In the future pass in the kw to
+ # output_for_cli()
+ attr = sorted(user.keys())
+ # Always have sn following givenname
+ try:
+ l = attr.index('givenname')
+ attr.remove('sn')
+ attr.insert(l+1, 'sn')
+ except ValueError:
+ pass
+
+ for a in attr:
+ if a != 'dn':
+ print "%s: %s" % (a, user[a])
+
+default_attributes = ['uid','givenname','sn','homeDirectory','loginshell']
+
+
+class user(Object):
+ """
+ User object.
+ """
+
+ takes_params = (
+ Str('givenname',
+ cli_name='first',
+ doc="User's first name",
+ ),
+ Str('sn',
+ cli_name='last',
+ doc="User's last name",
+ ),
+ Str('uid',
+ cli_name='user',
+ primary_key=True,
+ default_from=lambda givenname, sn: givenname[0] + sn,
+ normalizer=lambda value: value.lower(),
+ ),
+ Str('gecos?',
+ doc='GECOS field',
+ default_from=lambda uid: uid,
+ ),
+ Str('homedirectory?',
+ cli_name='home',
+ doc="User's home directory",
+ default_from=lambda uid: '/home/%s' % uid,
+ ),
+ Str('loginshell?',
+ cli_name='shell',
+ default=u'/bin/sh',
+ doc="User's Login shell",
+ ),
+ Str('krbprincipalname?',
+ cli_name='principal',
+ doc="User's Kerberos Principal name",
+ default_from=lambda uid: '%s@%s' % (uid, api.env.realm),
+ ),
+ Str('mailaddress?',
+ cli_name='email',
+ doc="User's e-mail address",
+ ),
+ Password('userpassword?',
+ cli_name='password',
+ doc="Set user's password",
+ ),
+ Str('groups?',
+ doc='Add account to one or more groups (comma-separated)',
+ ),
+ Int('uidnumber?',
+ cli_name='uid',
+ doc='The uid to use for this user. If not included one is automatically set.',
+ ),
+ )
+
+api.register(user)
+
+
+class user_add(crud.Add):
+ 'Add a new user.'
+
+ def execute(self, uid, **kw):
+ """
+ Execute the user-add operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry as it will be created in LDAP.
+
+ :param uid: The login name of the user being added.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'uid' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ kw['uid'] = uid
+ kw['dn'] = ldap.make_user_dn(uid)
+
+ # FIXME: enforce this elsewhere
+# if servercore.uid_too_long(kw['uid']):
+# raise errors.UsernameTooLong
+
+ # Get our configuration
+ config = ldap.get_ipa_config()
+
+ # Let us add in some missing attributes
+ if kw.get('homedirectory') is None:
+ kw['homedirectory'] = '%s/%s' % (config.get('ipahomesrootdir'), kw.get('uid'))
+ kw['homedirectory'] = kw['homedirectory'].replace('//', '/')
+ kw['homedirectory'] = kw['homedirectory'].rstrip('/')
+ if kw.get('loginshell') is None:
+ kw['loginshell'] = config.get('ipadefaultloginshell')
+ if kw.get('gecos') is None:
+ kw['gecos'] = kw['uid']
+
+ # If uidnumber is blank the the FDS dna_plugin will automatically
+ # assign the next value. So we don't have to do anything with it.
+
+ if not kw.get('gidnumber'):
+ try:
+ group_dn = ldap.find_entry_dn("cn", config.get('ipadefaultprimarygroup'))
+ default_group = ldap.retrieve(group_dn, ['dn','gidNumber'])
+ if default_group:
+ kw['gidnumber'] = default_group.get('gidnumber')
+ except errors.NotFound:
+ # Fake an LDAP error so we can return something useful to the kw
+ raise errors.NotFound, "The default group for new kws, '%s', cannot be found." % config.get('ipadefaultprimarygroup')
+ except Exception, e:
+ # catch everything else
+ raise e
+
+ if kw.get('krbprincipalname') is None:
+ kw['krbprincipalname'] = "%s@%s" % (kw.get('uid'), self.api.env.realm)
+
+ # FIXME. This is a hack so we can request separate First and Last
+ # name in the GUI.
+ if kw.get('cn') is None:
+ kw['cn'] = "%s %s" % (kw.get('givenname'),
+ kw.get('sn'))
+
+ # some required objectclasses
+ kw['objectClass'] = config.get('ipauserobjectclasses')
+
+ return ldap.create(**kw)
+
+ def output_for_cli(self, textui, result, *args, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_name(self.name)
+ textui.print_entry(result)
+ textui.print_dashed('Added user "%s"' % result['uid'])
+
+api.register(user_add)
+
+
+class user_del(crud.Del):
+ 'Delete an existing user.'
+
+ def execute(self, uid, **kw):
+ """Delete a user. Not to be confused with inactivate_user. This
+ makes the entry go away completely.
+
+ uid is the uid of the user to delete
+
+ The memberOf plugin handles removing the user from any other
+ groups.
+
+ :param uid: The login name of the user being added.
+ :param kw: Not used.
+ """
+ if uid == "admin":
+ # FIXME: do we still want a "special" user?
+ raise SyntaxError("admin required")
+# raise ipaerror.gen_exception(ipaerror.INPUT_ADMIN_REQUIRED)
+ self.log.info("IPA: user-del '%s'" % uid)
+
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("uid", uid)
+ return ldap.delete(dn)
+
+ def output_for_cli(self, textui, result, uid):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_plain('Deleted user "%s"' % uid)
+
+api.register(user_del)
+
+
+class user_mod(crud.Mod):
+ 'Edit an existing user.'
+ def execute(self, uid, **kw):
+ """
+ Execute the user-mod operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param uid: The login name of the user to retrieve.
+ :param kw: Keyword arguments for the other LDAP attributes.
+ """
+ assert 'uid' not in kw
+ assert 'dn' not in kw
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("uid", uid)
+ return ldap.update(dn, **kw)
+
+ def output_for_cli(self, textui, result, uid, **options):
+ """
+ Output result of this command to command line interface.
+ """
+ textui.print_name(self.name)
+ textui.print_entry(result)
+ textui.print_dashed('Updated user "%s"' % result['uid'])
+
+api.register(user_mod)
+
+
+class user_find(crud.Find):
+ 'Search the users.'
+ takes_options = (
+ Flag('all', doc='Retrieve all user attributes'),
+ )
+ def execute(self, term, **kw):
+ ldap = self.api.Backend.ldap
+
+ # Pull the list of searchable attributes out of the configuration.
+ config = ldap.get_ipa_config()
+ search_fields_conf_str = config.get('ipausersearchfields')
+ search_fields = search_fields_conf_str.split(",")
+
+ search_kw = {}
+ for s in search_fields:
+ search_kw[s] = term
+
+ object_type = ldap.get_object_type("uid")
+ if object_type and not kw.get('objectclass'):
+ search_kw['objectclass'] = object_type
+ if kw.get('all', False):
+ search_kw['attributes'] = ['*']
+ else:
+ search_kw['attributes'] = default_attributes
+ return ldap.search(**search_kw)
+
+ def output_for_cli(self, textui, result, uid, **options):
+ counter = result[0]
+ users = result[1:]
+ if counter == 0 or len(users) == 0:
+ textui.print_plain("No entries found")
+ return
+ if len(users) == 1:
+ textui.print_entry(users[0])
+ return
+ textui.print_name(self.name)
+ for u in users:
+ gn = u.get('givenname', '')
+ sn= u.get('sn', '')
+ textui.print_plain('%s %s:' % (gn, sn))
+ textui.print_entry(u)
+ textui.print_plain('')
+ if counter == -1:
+ textui.print_plain('These results are truncated.')
+ textui.print_plain('Please refine your search and try again.')
+ textui.print_count(users, '%d users matched')
+
+api.register(user_find)
+
+
+class user_show(crud.Get):
+ 'Examine an existing user.'
+ takes_options = (
+ Flag('all', doc='Retrieve all user attributes'),
+ )
+ def execute(self, uid, **kw):
+ """
+ Execute the user-show operation.
+
+ The dn should not be passed as a keyword argument as it is constructed
+ by this method.
+
+ Returns the entry
+
+ :param uid: The login name of the user to retrieve.
+ :param kw: "all" set to True = return all attributes
+ """
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("uid", uid)
+ # FIXME: should kw contain the list of attributes to display?
+ if kw.get('all', False):
+ return ldap.retrieve(dn)
+ else:
+ return ldap.retrieve(dn, default_attributes)
+
+ def output_for_cli(self, textui, result, uid, **options):
+ if result:
+ display_user(result)
+
+api.register(user_show)
+
+class user_lock(Command):
+ 'Lock a user account.'
+
+ takes_args = (
+ Str('uid', primary_key=True),
+ )
+
+ def execute(self, uid, **kw):
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("uid", uid)
+ return ldap.mark_entry_inactive(dn)
+
+ def output_for_cli(self, textui, result, uid):
+ if result:
+ textui.print_plain('Locked user "%s"' % uid)
+
+api.register(user_lock)
+
+
+class user_unlock(Command):
+ 'Unlock a user account.'
+
+ takes_args = (
+ Str('uid', primary_key=True),
+ )
+
+ def execute(self, uid, **kw):
+ ldap = self.api.Backend.ldap
+ dn = ldap.find_entry_dn("uid", uid)
+ return ldap.mark_entry_active(dn)
+
+ def output_for_cli(self, textui, result, uid):
+ if result:
+ textui.print_plain('Unlocked user "%s"' % uid)
+
+api.register(user_unlock)