diff options
Diffstat (limited to 'ipaclient/plugins')
-rw-r--r-- | ipaclient/plugins/automount.py | 53 | ||||
-rw-r--r-- | ipaclient/plugins/cert.py | 43 | ||||
-rw-r--r-- | ipaclient/plugins/certprofile.py | 28 | ||||
-rw-r--r-- | ipaclient/plugins/dns.py | 325 | ||||
-rw-r--r-- | ipaclient/plugins/hbacrule.py | 45 | ||||
-rw-r--r-- | ipaclient/plugins/hbactest.py | 55 | ||||
-rw-r--r-- | ipaclient/plugins/host.py | 49 | ||||
-rw-r--r-- | ipaclient/plugins/idrange.py | 89 | ||||
-rw-r--r-- | ipaclient/plugins/internal.py | 42 | ||||
-rw-r--r-- | ipaclient/plugins/migration.py | 71 | ||||
-rw-r--r-- | ipaclient/plugins/otptoken.py | 82 | ||||
-rw-r--r-- | ipaclient/plugins/service.py | 51 | ||||
-rw-r--r-- | ipaclient/plugins/sudorule.py | 57 | ||||
-rw-r--r-- | ipaclient/plugins/topology.py | 54 | ||||
-rw-r--r-- | ipaclient/plugins/trust.py | 51 | ||||
-rw-r--r-- | ipaclient/plugins/user.py | 82 | ||||
-rw-r--r-- | ipaclient/plugins/vault.py | 20 |
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.') |