summaryrefslogtreecommitdiffstats
path: root/ipaclient/plugins
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2016-04-28 10:15:01 +0200
committerJan Cholasta <jcholast@redhat.com>2016-06-03 09:00:34 +0200
commit4c7be74526bd89ed1b481f3a1ac4bb467ee0ea2c (patch)
tree3db268df3cfa41bd8a3c4655967e6931428b1ba9 /ipaclient/plugins
parent6cfb9d73d9701767d8b76d7ff5bbc080a6be9386 (diff)
downloadfreeipa-4c7be74526bd89ed1b481f3a1ac4bb467ee0ea2c.tar.gz
freeipa-4c7be74526bd89ed1b481f3a1ac4bb467ee0ea2c.tar.xz
freeipa-4c7be74526bd89ed1b481f3a1ac4bb467ee0ea2c.zip
ipalib: split off client-side plugin code into ipaclient
Provide client-side overrides for command plugins which implement any of the client-side `interactive_prompt_callback`, `forward` or `output_for_cli` methods and move the methods from the original plugins to the overrides. https://fedorahosted.org/freeipa/ticket/4739 Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipaclient/plugins')
-rw-r--r--ipaclient/plugins/automount.py53
-rw-r--r--ipaclient/plugins/cert.py43
-rw-r--r--ipaclient/plugins/certprofile.py28
-rw-r--r--ipaclient/plugins/dns.py325
-rw-r--r--ipaclient/plugins/hbacrule.py45
-rw-r--r--ipaclient/plugins/hbactest.py55
-rw-r--r--ipaclient/plugins/host.py49
-rw-r--r--ipaclient/plugins/idrange.py89
-rw-r--r--ipaclient/plugins/internal.py42
-rw-r--r--ipaclient/plugins/migration.py71
-rw-r--r--ipaclient/plugins/otptoken.py82
-rw-r--r--ipaclient/plugins/service.py51
-rw-r--r--ipaclient/plugins/sudorule.py57
-rw-r--r--ipaclient/plugins/topology.py54
-rw-r--r--ipaclient/plugins/trust.py51
-rw-r--r--ipaclient/plugins/user.py82
-rw-r--r--ipaclient/plugins/vault.py20
17 files changed, 1197 insertions, 0 deletions
diff --git a/ipaclient/plugins/automount.py b/ipaclient/plugins/automount.py
index 096315818..57a804446 100644
--- a/ipaclient/plugins/automount.py
+++ b/ipaclient/plugins/automount.py
@@ -22,11 +22,13 @@ import os
import six
+from ipaclient.frontend import MethodOverride
from ipalib import api, errors
from ipalib import Flag, Str
from ipalib.frontend import Command
from ipalib.plugable import Registry
from ipalib import _
+from ipapython.dn import DN
if six.PY3:
unicode = str
@@ -37,6 +39,57 @@ DEFAULT_MAPS = (u'auto.direct', )
DEFAULT_KEYS = (u'/-', )
+@register(override=True)
+class automountlocation_tofiles(MethodOverride):
+ def output_for_cli(self, textui, result, *keys, **options):
+ maps = result['result']['maps']
+ keys = result['result']['keys']
+ orphanmaps = result['result']['orphanmaps']
+ orphankeys = result['result']['orphankeys']
+
+ textui.print_plain('/etc/auto.master:')
+ for m in maps:
+ if m['automountinformation'][0].startswith('-'):
+ textui.print_plain(
+ '%s\t%s' % (
+ m['automountkey'][0], m['automountinformation'][0]
+ )
+ )
+ else:
+ textui.print_plain(
+ '%s\t/etc/%s' % (
+ m['automountkey'][0], m['automountinformation'][0]
+ )
+ )
+ for m in maps:
+ if m['automountinformation'][0].startswith('-'):
+ continue
+ info = m['automountinformation'][0]
+ textui.print_plain('---------------------------')
+ textui.print_plain('/etc/%s:' % info)
+ for k in keys[info]:
+ textui.print_plain(
+ '%s\t%s' % (
+ k['automountkey'][0], k['automountinformation'][0]
+ )
+ )
+
+ textui.print_plain('')
+ textui.print_plain(_('maps not connected to /etc/auto.master:'))
+ for m in orphanmaps:
+ textui.print_plain('---------------------------')
+ textui.print_plain('/etc/%s:' % m['automountmapname'])
+ for k in orphankeys:
+ if len(k) == 0: continue
+ dn = DN(k[0]['dn'])
+ if dn['automountmapname'] == m['automountmapname'][0]:
+ textui.print_plain(
+ '%s\t%s' % (
+ k[0]['automountkey'][0], k[0]['automountinformation'][0]
+ )
+ )
+
+
@register()
class automountlocation_import(Command):
__doc__ = _('Import automount files for a specific location.')
diff --git a/ipaclient/plugins/cert.py b/ipaclient/plugins/cert.py
new file mode 100644
index 000000000..722743e2f
--- /dev/null
+++ b/ipaclient/plugins/cert.py
@@ -0,0 +1,43 @@
+# Authors:
+# Andrew Wnuk <awnuk@redhat.com>
+# Jason Gerard DeRose <jderose@redhat.com>
+# John Dennis <jdennis@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import CommandOverride
+from ipalib import errors
+from ipalib import x509
+from ipalib import util
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+@register(override=True)
+class cert_show(CommandOverride):
+ def forward(self, *keys, **options):
+ if 'out' in options:
+ util.check_writable_file(options['out'])
+ result = super(cert_show, self).forward(*keys, **options)
+ if 'certificate' in result['result']:
+ x509.write_certificate(result['result']['certificate'], options['out'])
+ return result
+ else:
+ raise errors.NoCertificateError(entry=keys[-1])
+ else:
+ return super(cert_show, self).forward(*keys, **options)
diff --git a/ipaclient/plugins/certprofile.py b/ipaclient/plugins/certprofile.py
new file mode 100644
index 000000000..4fe1026d0
--- /dev/null
+++ b/ipaclient/plugins/certprofile.py
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
+#
+
+from ipaclient.frontend import MethodOverride
+from ipalib import util
+from ipalib.plugable import Registry
+from ipalib.text import _
+
+register = Registry()
+
+
+@register(override=True)
+class certprofile_show(MethodOverride):
+ def forward(self, *keys, **options):
+ if 'out' in options:
+ util.check_writable_file(options['out'])
+
+ result = super(certprofile_show, self).forward(*keys, **options)
+ if 'out' in options and 'config' in result['result']:
+ with open(options['out'], 'wb') as f:
+ f.write(result['result'].pop('config'))
+ result['summary'] = (
+ _("Profile configuration stored in file '%(file)s'")
+ % dict(file=options['out'])
+ )
+
+ return result
diff --git a/ipaclient/plugins/dns.py b/ipaclient/plugins/dns.py
new file mode 100644
index 000000000..6fca7cd24
--- /dev/null
+++ b/ipaclient/plugins/dns.py
@@ -0,0 +1,325 @@
+# Authors:
+# Martin Kosek <mkosek@redhat.com>
+# Pavel Zuna <pzuna@redhat.com>
+#
+# Copyright (C) 2010 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+import six
+
+from ipaclient.frontend import MethodOverride
+from ipalib import errors
+from ipalib.dns import (get_record_rrtype,
+ has_cli_options,
+ iterate_rrparams_by_parts,
+ record_name_format)
+from ipalib.plugable import Registry
+from ipalib import _, ngettext
+from ipapython.dnsutil import DNSName
+
+if six.PY3:
+ unicode = str
+
+register = Registry()
+
+# most used record types, always ask for those in interactive prompt
+_top_record_types = ('A', 'AAAA', )
+_rev_top_record_types = ('PTR', )
+_zone_top_record_types = ('NS', 'MX', 'LOC', )
+
+
+def __get_part_param(cmd, part, output_kw, default=None):
+ name = part.name
+ label = unicode(part.label)
+ optional = not part.required
+
+ output_kw[name] = cmd.prompt_param(part,
+ optional=optional,
+ label=label)
+
+
+def prompt_parts(rrtype, cmd, mod_dnsvalue=None):
+ mod_parts = None
+ if mod_dnsvalue is not None:
+ name = record_name_format % rrtype.lower()
+ mod_parts = cmd.api.Command.dnsrecord_split_parts(
+ name, mod_dnsvalue)['result']
+
+ user_options = {}
+ parts = [p for p in cmd.params() if 'dnsrecord_part' in p.flags]
+ if not parts:
+ return user_options
+
+ for part_id, part in enumerate(parts):
+ if mod_parts:
+ default = mod_parts[part_id]
+ else:
+ default = None
+
+ __get_part_param(cmd, part, user_options, default)
+
+ return user_options
+
+
+def prompt_missing_parts(rrtype, cmd, kw, prompt_optional=False):
+ user_options = {}
+ parts = [p for p in cmd.params() if 'dnsrecord_part' in p.flags]
+ if not parts:
+ return user_options
+
+ for part in parts:
+ name = part.name
+
+ if name in kw:
+ continue
+
+ optional = not part.required
+ if optional and not prompt_optional:
+ continue
+
+ default = part.get_default(**kw)
+ __get_part_param(cmd, part, user_options, default)
+
+ return user_options
+
+
+@register(override=True)
+class dnsrecord_add(MethodOverride):
+ no_option_msg = 'No options to add a specific record provided.\n' \
+ "Command help may be consulted for all supported record types."
+
+ def interactive_prompt_callback(self, kw):
+ try:
+ has_cli_options(self, kw, self.no_option_msg)
+
+ # Some DNS records were entered, do not use full interactive help
+ # We should still ask user for required parts of DNS parts he is
+ # trying to add in the same way we do for standard LDAP parameters
+ #
+ # Do not ask for required parts when any "extra" option is used,
+ # it can be used to fill all required params by itself
+ new_kw = {}
+ for rrparam in iterate_rrparams_by_parts(self, kw,
+ skip_extra=True):
+ rrtype = get_record_rrtype(rrparam.name)
+ user_options = prompt_missing_parts(rrtype, self, kw,
+ prompt_optional=False)
+ new_kw.update(user_options)
+ kw.update(new_kw)
+ return
+ except errors.OptionError:
+ pass
+
+ try:
+ idnsname = DNSName(kw['idnsname'])
+ except Exception as e:
+ raise errors.ValidationError(name='idnsname', error=unicode(e))
+
+ try:
+ zonename = DNSName(kw['dnszoneidnsname'])
+ except Exception as e:
+ raise errors.ValidationError(name='dnszoneidnsname', error=unicode(e))
+
+ # check zone type
+ if idnsname.is_empty():
+ common_types = u', '.join(_zone_top_record_types)
+ elif zonename.is_reverse():
+ common_types = u', '.join(_rev_top_record_types)
+ else:
+ common_types = u', '.join(_top_record_types)
+
+ self.Backend.textui.print_plain(_(u'Please choose a type of DNS resource record to be added'))
+ self.Backend.textui.print_plain(_(u'The most common types for this type of zone are: %s\n') %\
+ common_types)
+
+ ok = False
+ while not ok:
+ rrtype = self.Backend.textui.prompt(_(u'DNS resource record type'))
+
+ if rrtype is None:
+ return
+
+ try:
+ name = record_name_format % rrtype.lower()
+ param = self.params[name]
+
+ if 'no_option' in param.flags:
+ raise ValueError()
+ except (KeyError, ValueError):
+ all_types = u', '.join(get_record_rrtype(p.name)
+ for p in self.params()
+ if (get_record_rrtype(p.name) and
+ 'no_option' not in p.flags))
+ self.Backend.textui.print_plain(_(u'Invalid or unsupported type. Allowed values are: %s') % all_types)
+ continue
+ ok = True
+
+ user_options = prompt_parts(rrtype, self)
+ kw.update(user_options)
+
+
+@register(override=True)
+class dnsrecord_mod(MethodOverride):
+ no_option_msg = 'No options to modify a specific record provided.'
+
+ def interactive_prompt_callback(self, kw):
+ try:
+ has_cli_options(self, kw, self.no_option_msg, True)
+ except errors.OptionError:
+ pass
+ else:
+ # some record type entered, skip this helper
+ return
+
+ # get DNS record first so that the NotFound exception is raised
+ # before the helper would start
+ dns_record = self.api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
+
+ self.Backend.textui.print_plain(_("No option to modify specific record provided."))
+
+ # ask user for records to be removed
+ self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
+ record_params = []
+
+ for attr in dns_record:
+ try:
+ param = self.params[attr]
+ except KeyError:
+ continue
+ rrtype = get_record_rrtype(param.name)
+ if not rrtype:
+ continue
+
+ record_params.append((param, rrtype))
+ rec_type_content = u', '.join(dns_record[param.name])
+ self.Backend.textui.print_plain(u'%s: %s' % (param.label, rec_type_content))
+ self.Backend.textui.print_plain(u'')
+
+ # ask what records to remove
+ for param, rrtype in record_params:
+ rec_values = list(dns_record[param.name])
+ for rec_value in dns_record[param.name]:
+ rec_values.remove(rec_value)
+ mod_value = self.Backend.textui.prompt_yesno(
+ _("Modify %(name)s '%(value)s'?") % dict(name=param.label, value=rec_value), default=False)
+ if mod_value is True:
+ user_options = prompt_parts(rrtype, self,
+ mod_dnsvalue=rec_value)
+ kw[param.name] = [rec_value]
+ kw.update(user_options)
+
+ if rec_values:
+ self.Backend.textui.print_plain(ngettext(
+ u'%(count)d %(type)s record skipped. Only one value per DNS record type can be modified at one time.',
+ u'%(count)d %(type)s records skipped. Only one value per DNS record type can be modified at one time.',
+ 0) % dict(count=len(rec_values), type=rrtype))
+ break
+
+
+@register(override=True)
+class dnsrecord_del(MethodOverride):
+ no_option_msg = _('Neither --del-all nor options to delete a specific record provided.\n'\
+ "Command help may be consulted for all supported record types.")
+
+ def interactive_prompt_callback(self, kw):
+ if kw.get('del_all', False):
+ return
+ try:
+ has_cli_options(self, kw, self.no_option_msg)
+ except errors.OptionError:
+ pass
+ else:
+ # some record type entered, skip this helper
+ return
+
+ # get DNS record first so that the NotFound exception is raised
+ # before the helper would start
+ dns_record = self.api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
+
+ self.Backend.textui.print_plain(_("No option to delete specific record provided."))
+ user_del_all = self.Backend.textui.prompt_yesno(_("Delete all?"), default=False)
+
+ if user_del_all is True:
+ kw['del_all'] = True
+ return
+
+ # ask user for records to be removed
+ self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
+ present_params = []
+
+ for attr in dns_record:
+ try:
+ param = self.params[attr]
+ except KeyError:
+ continue
+ if not get_record_rrtype(param.name):
+ continue
+
+ present_params.append(param)
+ rec_type_content = u', '.join(dns_record[param.name])
+ self.Backend.textui.print_plain(u'%s: %s' % (param.label, rec_type_content))
+ self.Backend.textui.print_plain(u'')
+
+ # ask what records to remove
+ for param in present_params:
+ deleted_values = []
+ for rec_value in dns_record[param.name]:
+ user_del_value = self.Backend.textui.prompt_yesno(
+ _("Delete %(name)s '%(value)s'?")
+ % dict(name=param.label, value=rec_value), default=False)
+ if user_del_value is True:
+ deleted_values.append(rec_value)
+ if deleted_values:
+ kw[param.name] = tuple(deleted_values)
+
+
+@register(override=True)
+class dnsconfig_mod(MethodOverride):
+ def interactive_prompt_callback(self, kw):
+
+ # show informative message on client side
+ # server cannot send messages asynchronous
+ if kw.get('idnsforwarders', False):
+ self.Backend.textui.print_plain(
+ _("Server will check DNS forwarder(s)."))
+ self.Backend.textui.print_plain(
+ _("This may take some time, please wait ..."))
+
+
+@register(override=True)
+class dnsforwardzone_add(MethodOverride):
+ def interactive_prompt_callback(self, kw):
+ # show informative message on client side
+ # server cannot send messages asynchronous
+ if kw.get('idnsforwarders', False):
+ self.Backend.textui.print_plain(
+ _("Server will check DNS forwarder(s)."))
+ self.Backend.textui.print_plain(
+ _("This may take some time, please wait ..."))
+
+
+@register(override=True)
+class dnsforwardzone_mod(MethodOverride):
+ def interactive_prompt_callback(self, kw):
+ # show informative message on client side
+ # server cannot send messages asynchronous
+ if kw.get('idnsforwarders', False):
+ self.Backend.textui.print_plain(
+ _("Server will check DNS forwarder(s)."))
+ self.Backend.textui.print_plain(
+ _("This may take some time, please wait ..."))
diff --git a/ipaclient/plugins/hbacrule.py b/ipaclient/plugins/hbacrule.py
new file mode 100644
index 000000000..826ed7a88
--- /dev/null
+++ b/ipaclient/plugins/hbacrule.py
@@ -0,0 +1,45 @@
+# Authors:
+# Pavel Zuna <pzuna@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+#@register()
+class hbacrule_add_accesstime(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_name(self.name)
+ textui.print_dashed(
+ 'Added access time "%s" to HBAC rule "%s"' % (
+ options['accesstime'], cn
+ )
+ )
+
+
+#@register()
+class hbacrule_remove_accesstime(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_name(self.name)
+ textui.print_dashed(
+ 'Removed access time "%s" from HBAC rule "%s"' % (
+ options['accesstime'], cn
+ )
+ )
diff --git a/ipaclient/plugins/hbactest.py b/ipaclient/plugins/hbactest.py
new file mode 100644
index 000000000..10a640a5a
--- /dev/null
+++ b/ipaclient/plugins/hbactest.py
@@ -0,0 +1,55 @@
+# Authors:
+# Alexander Bokovoy <abokovoy@redhat.com>
+#
+# Copyright (C) 2011 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import CommandOverride
+from ipalib.plugable import Registry
+
+import six
+
+if six.PY3:
+ unicode = str
+
+register = Registry()
+
+
+@register(override=True)
+class hbactest(CommandOverride):
+ def output_for_cli(self, textui, output, *args, **options):
+ """
+ Command.output_for_cli() uses --all option to decide whether to print detailed output.
+ We use --detail to allow that, thus we need to redefine output_for_cli().
+ """
+ # Note that we don't actually use --detail below to see if details need
+ # to be printed as our execute() method will return None for corresponding
+ # entries and None entries will be skipped.
+ for o in self.output:
+ outp = self.output[o]
+ if 'no_display' in outp.flags:
+ continue
+ result = output[o]
+ if isinstance(result, (list, tuple)):
+ textui.print_attribute(unicode(outp.doc), result, '%s: %s', 1, True)
+ elif isinstance(result, (unicode, bool)):
+ if o == 'summary':
+ textui.print_summary(result)
+ else:
+ textui.print_indented(result)
+
+ # Propagate integer value for result. It will give proper command line result for scripts
+ return int(not output['value'])
diff --git a/ipaclient/plugins/host.py b/ipaclient/plugins/host.py
new file mode 100644
index 000000000..a346226b5
--- /dev/null
+++ b/ipaclient/plugins/host.py
@@ -0,0 +1,49 @@
+# Authors:
+# Rob Crittenden <rcritten@redhat.com>
+# Pavel Zuna <pzuna@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib import errors, util
+from ipalib.plugable import Registry
+from ipalib import _
+from ipalib import x509
+
+register = Registry()
+
+
+@register(override=True)
+class host_show(MethodOverride):
+ def forward(self, *keys, **options):
+ if 'out' in options:
+ util.check_writable_file(options['out'])
+ result = super(host_show, self).forward(*keys, **options)
+ if 'usercertificate' in result['result']:
+ x509.write_certificate_list(
+ result['result']['usercertificate'],
+ options['out']
+ )
+ result['summary'] = (
+ _('Certificate(s) stored in file \'%(file)s\'')
+ % dict(file=options['out'])
+ )
+ return result
+ else:
+ raise errors.NoCertificateError(entry=keys[-1])
+ else:
+ return super(host_show, self).forward(*keys, **options)
diff --git a/ipaclient/plugins/idrange.py b/ipaclient/plugins/idrange.py
new file mode 100644
index 000000000..83ad8fdcf
--- /dev/null
+++ b/ipaclient/plugins/idrange.py
@@ -0,0 +1,89 @@
+# Authors:
+# Sumit Bose <sbose@redhat.com>
+#
+# Copyright (C) 2012 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+from ipalib import api
+
+register = Registry()
+
+
+@register(override=True)
+class idrange_add(MethodOverride):
+ def interactive_prompt_callback(self, kw):
+ """
+ Ensure that rid-base is prompted for when dom-sid is specified.
+
+ Also ensure that secondary-rid-base is prompted for when rid-base is
+ specified and vice versa, in case that dom-sid was not specified.
+
+ Also ensure that rid-base and secondary-rid-base is prompted for
+ if ipa-adtrust-install has been run on the system.
+ """
+
+ # dom-sid can be specified using dom-sid or dom-name options
+
+ # it can be also set using --setattr or --addattr, in these cases
+ # we will not prompt, but raise an ValidationError later
+
+ dom_sid_set = any(dom_id in kw for dom_id in
+ ('ipanttrusteddomainname', 'ipanttrusteddomainsid'))
+
+ rid_base = kw.get('ipabaserid', None)
+ secondary_rid_base = kw.get('ipasecondarybaserid', None)
+ range_type = kw.get('iparangetype', None)
+
+ def set_from_prompt(param):
+ value = self.prompt_param(self.params[param])
+ update = {param: value}
+ kw.update(update)
+
+ if dom_sid_set:
+ # This is a trusted range
+
+ # Prompt for RID base if domain SID / name was given
+ if rid_base is None and range_type != u'ipa-ad-trust-posix':
+ set_from_prompt('ipabaserid')
+
+ else:
+ # This is a local range
+ # Find out whether ipa-adtrust-install has been ran
+ adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
+
+ if adtrust_is_enabled:
+ # If ipa-adtrust-install has been ran, all local ranges
+ # require both RID base and secondary RID base
+
+ if rid_base is None:
+ set_from_prompt('ipabaserid')
+
+ if secondary_rid_base is None:
+ set_from_prompt('ipasecondarybaserid')
+
+ else:
+ # This is a local range on a server with no adtrust support
+
+ # Prompt for secondary RID base only if RID base was given
+ if rid_base is not None and secondary_rid_base is None:
+ set_from_prompt('ipasecondarybaserid')
+
+ # Symetrically, prompt for RID base if secondary RID base was
+ # given
+ if rid_base is None and secondary_rid_base is not None:
+ set_from_prompt('ipabaserid')
diff --git a/ipaclient/plugins/internal.py b/ipaclient/plugins/internal.py
new file mode 100644
index 000000000..65cbbe7a5
--- /dev/null
+++ b/ipaclient/plugins/internal.py
@@ -0,0 +1,42 @@
+# Authors:
+# Pavel Zuna <pzuna@redhat.com>
+# Adam Young <ayoung@redhat.com>
+# Endi S. Dewata <edewata@redhat.com>
+#
+# Copyright (c) 2010 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+import json
+
+from ipaclient.frontend import CommandOverride
+from ipalib.util import json_serialize
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+@register(override=True)
+class json_metadata(CommandOverride):
+ def output_for_cli(self, textui, result, *args, **options):
+ print(json.dumps(result, default=json_serialize))
+
+
+@register(override=True)
+class i18n_messages(CommandOverride):
+ def output_for_cli(self, textui, result, *args, **options):
+ print(json.dumps(result, default=json_serialize))
diff --git a/ipaclient/plugins/migration.py b/ipaclient/plugins/migration.py
new file mode 100644
index 000000000..1a184b963
--- /dev/null
+++ b/ipaclient/plugins/migration.py
@@ -0,0 +1,71 @@
+# Authors:
+# Pavel Zuna <pzuna@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import six
+
+from ipaclient.frontend import CommandOverride
+from ipalib.plugable import Registry
+from ipalib import _
+
+if six.PY3:
+ unicode = str
+
+register = Registry()
+
+
+@register(override=True)
+class migrate_ds(CommandOverride):
+ migrate_order = ('user', 'group')
+
+ migration_disabled_msg = _('''\
+Migration mode is disabled. Use \'ipa config-mod\' to enable it.''')
+
+ pwd_migration_msg = _('''\
+Passwords have been migrated in pre-hashed format.
+IPA is unable to generate Kerberos keys unless provided
+with clear text passwords. All migrated users need to
+login at https://your.domain/ipa/migration/ before they
+can use their Kerberos accounts.''')
+
+ def output_for_cli(self, textui, result, ldapuri, bindpw, **options):
+ textui.print_name(self.name)
+ if not result['enabled']:
+ textui.print_plain(self.migration_disabled_msg)
+ return 1
+ if not result['compat']:
+ textui.print_plain("The compat plug-in is enabled. This can increase the memory requirements during migration. Disable the compat plug-in with \'ipa-compat-manage disable\' or re-run this script with \'--with-compat\' option.")
+ return 1
+ any_migrated = any(result['result'].values())
+ textui.print_plain('Migrated:')
+ textui.print_entry1(
+ result['result'], attr_order=self.migrate_order,
+ one_value_per_line=False
+ )
+ for ldap_obj_name in self.migrate_order:
+ textui.print_plain('Failed %s:' % ldap_obj_name)
+ textui.print_entry1(
+ result['failed'][ldap_obj_name], attr_order=self.migrate_order,
+ one_value_per_line=True,
+ )
+ textui.print_plain('-' * len(self.name))
+ if not any_migrated:
+ textui.print_plain('No users/groups were migrated from %s' %
+ ldapuri)
+ return 1
+ textui.print_plain(unicode(self.pwd_migration_msg))
diff --git a/ipaclient/plugins/otptoken.py b/ipaclient/plugins/otptoken.py
index 66a457cde..3b393794c 100644
--- a/ipaclient/plugins/otptoken.py
+++ b/ipaclient/plugins/otptoken.py
@@ -17,14 +17,24 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import print_function
+import sys
+
+from ipaclient.frontend import MethodOverride
from ipalib import api, Str, Password, _
+from ipalib.messages import add_message, ResultFormattingError
from ipalib.plugable import Registry
from ipalib.frontend import Local
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.nsslib import NSSConnection
+from ipapython.version import API_VERSION
+
+import locale
+import qrcode
import six
+from six import StringIO
from six.moves import urllib
if six.PY3:
@@ -33,6 +43,78 @@ if six.PY3:
register = Registry()
+@register(override=True)
+class otptoken_add(MethodOverride):
+ def _get_qrcode(self, output, uri, version):
+ # Print QR code to terminal if specified
+ qr_output = StringIO()
+ qr = qrcode.QRCode()
+ qr.add_data(uri)
+ qr.make()
+ qr.print_ascii(out=qr_output, tty=False)
+
+ encoding = getattr(sys.stdout, 'encoding', None)
+ if encoding is None:
+ encoding = locale.getpreferredencoding(False)
+
+ try:
+ qr_code = qr_output.getvalue().decode(encoding)
+ except UnicodeError:
+ add_message(
+ version,
+ output,
+ message=ResultFormattingError(
+ message=_("Unable to display QR code using the configured "
+ "output encoding. Please use the token URI to "
+ "configure you OTP device")
+ )
+ )
+ return None
+
+ if sys.stdout.isatty():
+ output_width = self.api.Backend.textui.get_tty_width()
+ qr_code_width = len(qr_code.splitlines()[0])
+ if qr_code_width > output_width:
+ add_message(
+ version,
+ output,
+ message=ResultFormattingError(
+ message=_(
+ "QR code width is greater than that of the output "
+ "tty. Please resize your terminal.")
+ )
+ )
+
+ return qr
+
+ def output_for_cli(self, textui, output, *args, **options):
+ # copy-pasted from ipalib/Frontend.__do_call()
+ # because option handling is broken on client-side
+ if 'version' in options:
+ pass
+ elif self.api.env.skip_version_check:
+ options['version'] = u'2.0'
+ else:
+ options['version'] = API_VERSION
+
+ uri = output['result'].get('uri', None)
+
+ if uri is not None and not options.get('no_qrcode', False):
+ qr = self._get_qrcode(output, uri, options['version'])
+ else:
+ qr = None
+
+ rv = super(otptoken_add, self).output_for_cli(
+ textui, output, *args, **options)
+
+ if qr is not None:
+ print("\n")
+ qr.print_ascii(tty=sys.stdout.isatty())
+ print("\n")
+
+ return rv
+
+
class HTTPSHandler(urllib.request.HTTPSHandler):
"Opens SSL HTTPS connections that perform hostname validation."
diff --git a/ipaclient/plugins/service.py b/ipaclient/plugins/service.py
new file mode 100644
index 000000000..72783b617
--- /dev/null
+++ b/ipaclient/plugins/service.py
@@ -0,0 +1,51 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+# Rob Crittenden <rcritten@redhat.com>
+# Pavel Zuna <pzuna@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib import errors
+from ipalib.plugable import Registry
+from ipalib import x509
+from ipalib import _
+from ipalib import util
+
+register = Registry()
+
+
+@register(override=True)
+class service_show(MethodOverride):
+ def forward(self, *keys, **options):
+ if 'out' in options:
+ util.check_writable_file(options['out'])
+ result = super(service_show, self).forward(*keys, **options)
+ if 'usercertificate' in result['result']:
+ x509.write_certificate_list(
+ result['result']['usercertificate'],
+ options['out']
+ )
+ result['summary'] = (
+ _('Certificate(s) stored in file \'%(file)s\'')
+ % dict(file=options['out'])
+ )
+ return result
+ else:
+ raise errors.NoCertificateError(entry=keys[-1])
+ else:
+ return super(service_show, self).forward(*keys, **options)
diff --git a/ipaclient/plugins/sudorule.py b/ipaclient/plugins/sudorule.py
new file mode 100644
index 000000000..4098eb809
--- /dev/null
+++ b/ipaclient/plugins/sudorule.py
@@ -0,0 +1,57 @@
+# Authors:
+# Jr Aquino <jr.aquino@citrixonline.com>
+#
+# Copyright (C) 2010-2014 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+from ipalib import _
+
+register = Registry()
+
+
+@register(override=True)
+class sudorule_enable(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_dashed(_('Enabled Sudo Rule "%s"') % cn)
+
+
+@register(override=True)
+class sudorule_disable(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_dashed(_('Disabled Sudo Rule "%s"') % cn)
+
+
+@register(override=True)
+class sudorule_add_option(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_dashed(
+ _('Added option "%(option)s" to Sudo Rule "%(rule)s"')
+ % dict(option=options['ipasudoopt'], rule=cn))
+
+ super(sudorule_add_option, self).output_for_cli(textui, result, cn,
+ **options)
+
+
+@register(override=True)
+class sudorule_remove_option(MethodOverride):
+ def output_for_cli(self, textui, result, cn, **options):
+ textui.print_dashed(
+ _('Removed option "%(option)s" from Sudo Rule "%(rule)s"')
+ % dict(option=options['ipasudoopt'], rule=cn))
+ super(sudorule_remove_option, self).output_for_cli(textui, result, cn,
+ **options)
diff --git a/ipaclient/plugins/topology.py b/ipaclient/plugins/topology.py
new file mode 100644
index 000000000..522dcfa9a
--- /dev/null
+++ b/ipaclient/plugins/topology.py
@@ -0,0 +1,54 @@
+#
+# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
+#
+
+import six
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+from ipalib import _
+
+if six.PY3:
+ unicode = str
+
+register = Registry()
+
+
+@register(override=True)
+class topologysuffix_verify(MethodOverride):
+ def output_for_cli(self, textui, output, *args, **options):
+
+ in_order = output['result']['in_order']
+ connect_errors = output['result']['connect_errors']
+ max_agmts_errors = output['result']['max_agmts_errors']
+
+ if in_order:
+ header = _('Replication topology of suffix "%(suffix)s" '
+ 'is in order.')
+ else:
+ header = _('Replication topology of suffix "%(suffix)s" contains '
+ 'errors.')
+ textui.print_h1(header % {'suffix': args[0]})
+
+ if connect_errors:
+ textui.print_dashed(unicode(_('Topology is disconnected')))
+ for err in connect_errors:
+ msg = _("Server %(srv)s can't contact servers: %(replicas)s")
+ msg = msg % {'srv': err[0], 'replicas': ', '.join(err[2])}
+ textui.print_indented(msg)
+
+ if max_agmts_errors:
+ textui.print_dashed(unicode(_('Recommended maximum number of '
+ 'agreements per replica exceeded')))
+ textui.print_attribute(
+ unicode(_("Maximum number of agreements per replica")),
+ [output['result']['max_agmts']]
+ )
+ for err in max_agmts_errors:
+ msg = _('Server "%(srv)s" has %(n)d agreements with servers:')
+ msg = msg % {'srv': err[0], 'n': len(err[1])}
+ textui.print_indented(msg)
+ for replica in err[1]:
+ textui.print_indented(replica, 2)
+
+ return 0
diff --git a/ipaclient/plugins/trust.py b/ipaclient/plugins/trust.py
new file mode 100644
index 000000000..004c870c3
--- /dev/null
+++ b/ipaclient/plugins/trust.py
@@ -0,0 +1,51 @@
+# Authors:
+# Alexander Bokovoy <abokovoy@redhat.com>
+# Martin Kosek <mkosek@redhat.com>
+#
+# Copyright (C) 2011 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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+@register(override=True)
+class trust_add(MethodOverride):
+ def interactive_prompt_callback(self, kw):
+ """
+ Also ensure that realm_admin is prompted for if --admin or
+ --trust-secret is not specified when 'ipa trust-add' is run on the
+ system.
+
+ Also ensure that realm_passwd is prompted for if --password or
+ --trust-secret is not specified when 'ipa trust-add' is run on the
+ system.
+ """
+
+ trust_secret = kw.get('trust_secret')
+ realm_admin = kw.get('realm_admin')
+ realm_passwd = kw.get('realm_passwd')
+
+ if trust_secret is None:
+ if realm_admin is None:
+ kw['realm_admin'] = self.prompt_param(
+ self.params['realm_admin'])
+
+ if realm_passwd is None:
+ kw['realm_passwd'] = self.Backend.textui.prompt_password(
+ self.params['realm_passwd'].label, confirm=False)
diff --git a/ipaclient/plugins/user.py b/ipaclient/plugins/user.py
new file mode 100644
index 000000000..ccff9bbbc
--- /dev/null
+++ b/ipaclient/plugins/user.py
@@ -0,0 +1,82 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+# Pavel Zuna <pzuna@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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib import errors
+from ipalib import Flag
+from ipalib import util
+from ipalib.plugable import Registry
+from ipalib import _
+from ipalib import x509
+
+register = Registry()
+
+
+@register(override=True)
+class user_del(MethodOverride):
+ def get_options(self):
+ for option in super(user_del, self).get_options():
+ yield option
+ yield Flag(
+ 'preserve?',
+ include='cli',
+ doc=_('Delete a user, keeping the entry available for future use'),
+ )
+ yield Flag(
+ 'no_preserve?',
+ include='cli',
+ doc=_('Delete a user'),
+ )
+
+ def forward(self, *keys, **options):
+ if self.api.env.context == 'cli':
+ no_preserve = options.pop('no_preserve', False)
+ preserve = options.pop('preserve', False)
+ if no_preserve and preserve:
+ raise errors.MutuallyExclusiveError(
+ reason=_("preserve and no-preserve cannot be both set"))
+ elif no_preserve:
+ options['preserve'] = False
+ elif preserve:
+ options['preserve'] = True
+
+ return super(user_del, self).forward(*keys, **options)
+
+
+@register(override=True)
+class user_show(MethodOverride):
+ def forward(self, *keys, **options):
+ if 'out' in options:
+ util.check_writable_file(options['out'])
+ result = super(user_show, self).forward(*keys, **options)
+ if 'usercertificate' in result['result']:
+ x509.write_certificate_list(
+ result['result']['usercertificate'],
+ options['out']
+ )
+ result['summary'] = (
+ _('Certificate(s) stored in file \'%(file)s\'')
+ % dict(file=options['out'])
+ )
+ return result
+ else:
+ raise errors.NoCertificateError(entry=keys[-1])
+ else:
+ return super(user_show, self).forward(*keys, **options)
diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index ca132e8e6..945f390c0 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -36,6 +36,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key,\
import nss.nss as nss
+from ipaclient.frontend import MethodOverride
from ipalib.frontend import Local
from ipalib import errors
from ipalib import Bytes, Flag, Str
@@ -492,6 +493,25 @@ class vault_mod(Local):
return response
+@register(override=True)
+class vaultconfig_show(MethodOverride):
+ def forward(self, *args, **options):
+
+ file = options.get('transport_out')
+
+ # don't send these parameters to server
+ if 'transport_out' in options:
+ del options['transport_out']
+
+ response = super(vaultconfig_show, self).forward(*args, **options)
+
+ if file:
+ with open(file, 'w') as f:
+ f.write(response['result']['transport_cert'])
+
+ return response
+
+
@register()
class vault_archive(Local):
__doc__ = _('Archive data into a vault.')