Imported Upstream version 4.6.2
This commit is contained in:
7
ipaclient/plugins/__init__.py
Normal file
7
ipaclient/plugins/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Sub-package containing all client plugins.
|
||||
"""
|
||||
35
ipaclient/plugins/automember.py
Normal file
35
ipaclient/plugins/automember.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Authors:
|
||||
# Jr Aquino <jr.aquino@citrix.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.frontend import Str
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class automember_add_condition(MethodOverride):
|
||||
has_output_params = (
|
||||
Str('failed',
|
||||
label=_('Failed to add'),
|
||||
flags=['suppress_empty'],
|
||||
),
|
||||
)
|
||||
303
ipaclient/plugins/automount.py
Normal file
303
ipaclient/plugins/automount.py
Normal file
@@ -0,0 +1,303 @@
|
||||
# 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/>.
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import api, errors
|
||||
from ipalib import Flag, Str
|
||||
from ipalib.frontend import Command, Method, Object
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.util import classproperty
|
||||
from ipalib import _
|
||||
from ipapython.dn import DN
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
register = Registry()
|
||||
|
||||
DEFAULT_MAPS = (u'auto.direct', )
|
||||
DEFAULT_KEYS = (u'/-', )
|
||||
|
||||
|
||||
@register(no_fail=True)
|
||||
class _fake_automountlocation(Object):
|
||||
name = 'automountlocation'
|
||||
|
||||
|
||||
@register(no_fail=True)
|
||||
class _fake_automountlocation_show(Method):
|
||||
name = 'automountlocation_show'
|
||||
NO_CLI = True
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class automountlocation_tofiles(MethodOverride):
|
||||
@classmethod
|
||||
def __NO_CLI_getter(cls):
|
||||
return (api.Command.get_plugin('automountlocation_show') is
|
||||
_fake_automountlocation_show)
|
||||
|
||||
NO_CLI = classproperty(__NO_CLI_getter)
|
||||
|
||||
@property
|
||||
def api_version(self):
|
||||
return self.api.Command.automountlocation_show.api_version
|
||||
|
||||
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.')
|
||||
|
||||
takes_args = (
|
||||
Str('masterfile',
|
||||
label=_('Master file'),
|
||||
doc=_('Automount master file.'),
|
||||
),
|
||||
)
|
||||
|
||||
takes_options = (
|
||||
Flag('continue?',
|
||||
cli_name='continue',
|
||||
doc=_('Continuous operation mode. Errors are reported but the process continues.'),
|
||||
),
|
||||
)
|
||||
|
||||
def get_args(self):
|
||||
for arg in self.api.Command.automountlocation_show.args():
|
||||
yield arg
|
||||
for arg in super(automountlocation_import, self).get_args():
|
||||
yield arg
|
||||
|
||||
def __read_mapfile(self, filename):
|
||||
try:
|
||||
fp = open(filename, 'r')
|
||||
map = fp.readlines()
|
||||
fp.close()
|
||||
except IOError as e:
|
||||
if e.errno == 2:
|
||||
raise errors.NotFound(
|
||||
reason=_('File %(file)s not found') % {'file': filename}
|
||||
)
|
||||
else:
|
||||
raise
|
||||
return map
|
||||
|
||||
def forward(self, *args, **options):
|
||||
"""
|
||||
The basic idea is to read the master file and create all the maps
|
||||
we need, then read each map file and add all the keys for the map.
|
||||
"""
|
||||
self.api.Command['automountlocation_show'](args[0])
|
||||
|
||||
result = {'maps':[], 'keys':[], 'skipped':[], 'duplicatekeys':[], 'duplicatemaps':[]}
|
||||
maps = {}
|
||||
master = self.__read_mapfile(args[1])
|
||||
for m in master:
|
||||
if m.startswith('#'):
|
||||
continue
|
||||
m = m.rstrip()
|
||||
if m.startswith('+'):
|
||||
result['skipped'].append([m,args[1]])
|
||||
continue
|
||||
if len(m) == 0:
|
||||
continue
|
||||
am = m.split(None)
|
||||
if len(am) < 2:
|
||||
continue
|
||||
|
||||
if am[1].startswith('/'):
|
||||
mapfile = am[1].replace('"','')
|
||||
am[1] = os.path.basename(am[1])
|
||||
maps[am[1]] = mapfile
|
||||
|
||||
# Add a new key to the auto.master map for the new map file
|
||||
try:
|
||||
api.Command['automountkey_add'](
|
||||
args[0],
|
||||
u'auto.master',
|
||||
automountkey=unicode(am[0]),
|
||||
automountinformation=unicode(' '.join(am[1:])))
|
||||
result['keys'].append([am[0], u'auto.master'])
|
||||
except errors.DuplicateEntry:
|
||||
if unicode(am[0]) in DEFAULT_KEYS:
|
||||
# ignore conflict when the key was pre-created by the framework
|
||||
pass
|
||||
elif options.get('continue', False):
|
||||
result['duplicatekeys'].append(am[0])
|
||||
else:
|
||||
raise errors.DuplicateEntry(
|
||||
message=_('key %(key)s already exists') % dict(
|
||||
key=am[0]))
|
||||
# Add the new map
|
||||
if not am[1].startswith('-'):
|
||||
try:
|
||||
api.Command['automountmap_add'](args[0], unicode(am[1]))
|
||||
result['maps'].append(am[1])
|
||||
except errors.DuplicateEntry:
|
||||
if unicode(am[1]) in DEFAULT_MAPS:
|
||||
# ignore conflict when the map was pre-created by the framework
|
||||
pass
|
||||
elif options.get('continue', False):
|
||||
result['duplicatemaps'].append(am[0])
|
||||
else:
|
||||
raise errors.DuplicateEntry(
|
||||
message=_('map %(map)s already exists') % dict(
|
||||
map=am[1]))
|
||||
|
||||
# Now iterate over the map files and add the keys. To handle
|
||||
# continuation lines I'll make a pass through it to skip comments
|
||||
# etc and also to combine lines.
|
||||
for m in maps:
|
||||
map = self.__read_mapfile(maps[m])
|
||||
lines = []
|
||||
cont = ''
|
||||
for x in map:
|
||||
if x.startswith('#'):
|
||||
continue
|
||||
x = x.rstrip()
|
||||
if x.startswith('+'):
|
||||
result['skipped'].append([m, maps[m]])
|
||||
continue
|
||||
if len(x) == 0:
|
||||
continue
|
||||
if x.endswith("\\"):
|
||||
cont = cont + x[:-1] + ' '
|
||||
else:
|
||||
lines.append(cont + x)
|
||||
cont=''
|
||||
for x in lines:
|
||||
am = x.split(None)
|
||||
key = unicode(am[0].replace('"',''))
|
||||
try:
|
||||
api.Command['automountkey_add'](
|
||||
args[0],
|
||||
unicode(m),
|
||||
automountkey=key,
|
||||
automountinformation=unicode(' '.join(am[1:])))
|
||||
result['keys'].append([key,m])
|
||||
except errors.DuplicateEntry as e:
|
||||
if options.get('continue', False):
|
||||
result['duplicatekeys'].append(am[0])
|
||||
else:
|
||||
raise e
|
||||
|
||||
return dict(result=result)
|
||||
|
||||
def output_for_cli(self, textui, result, *keys, **options):
|
||||
maps = result['result']['maps']
|
||||
keys = result['result']['keys']
|
||||
duplicatemaps = result['result']['duplicatemaps']
|
||||
duplicatekeys = result['result']['duplicatekeys']
|
||||
skipped = result['result']['skipped']
|
||||
|
||||
textui.print_plain(_('Imported maps:'))
|
||||
for m in maps:
|
||||
textui.print_plain(
|
||||
_('Added %(map)s') % dict(map=m)
|
||||
)
|
||||
textui.print_plain('')
|
||||
|
||||
textui.print_plain(_('Imported keys:'))
|
||||
for k in keys:
|
||||
textui.print_plain(
|
||||
_('Added %(src)s to %(dst)s') % dict(
|
||||
src=k[0], dst=k[1]
|
||||
)
|
||||
)
|
||||
textui.print_plain('')
|
||||
|
||||
if len(skipped) > 0:
|
||||
textui.print_plain(_('Ignored keys:'))
|
||||
for k in skipped:
|
||||
textui.print_plain(
|
||||
_('Ignored %(src)s to %(dst)s') % dict(
|
||||
src=k[0], dst=k[1]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if options.get('continue', False) and len(duplicatemaps) > 0:
|
||||
textui.print_plain('')
|
||||
textui.print_plain(_('Duplicate maps skipped:'))
|
||||
for m in duplicatemaps:
|
||||
textui.print_plain(
|
||||
_('Skipped %(map)s') % dict(map=m)
|
||||
)
|
||||
|
||||
|
||||
if options.get('continue', False) and len(duplicatekeys) > 0:
|
||||
textui.print_plain('')
|
||||
textui.print_plain(_('Duplicate keys skipped:'))
|
||||
for k in duplicatekeys:
|
||||
textui.print_plain(
|
||||
_('Skipped %(key)s') % dict(key=k)
|
||||
)
|
||||
56
ipaclient/plugins/ca.py
Normal file
56
ipaclient/plugins/ca.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import errors, util, x509, Str
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
class WithCertOutArgs(MethodOverride):
|
||||
|
||||
takes_options = (
|
||||
Str(
|
||||
'certificate_out?',
|
||||
doc=_('Write certificate (chain if --chain used) to file'),
|
||||
include='cli',
|
||||
cli_metavar='FILE',
|
||||
),
|
||||
)
|
||||
|
||||
def forward(self, *keys, **options):
|
||||
filename = None
|
||||
if 'certificate_out' in options:
|
||||
filename = options.pop('certificate_out')
|
||||
try:
|
||||
util.check_writable_file(filename)
|
||||
except errors.FileError as e:
|
||||
raise errors.ValidationError(name='certificate-out',
|
||||
error=str(e))
|
||||
|
||||
result = super(WithCertOutArgs, self).forward(*keys, **options)
|
||||
if filename:
|
||||
if options.get('chain', False):
|
||||
certs = (x509.load_der_x509_certificate(c)
|
||||
for c in result['result']['certificate_chain'])
|
||||
else:
|
||||
certs = [
|
||||
x509.load_der_x509_certificate(
|
||||
result['result']['certificate'])
|
||||
]
|
||||
x509.write_certificate_list(certs, filename)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class ca_add(WithCertOutArgs):
|
||||
pass
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class ca_show(WithCertOutArgs):
|
||||
pass
|
||||
212
ipaclient/plugins/cert.py
Normal file
212
ipaclient/plugins/cert.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# 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/>.
|
||||
|
||||
import base64
|
||||
|
||||
import six
|
||||
|
||||
from ipaclient import csrgen
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import errors
|
||||
from ipalib import x509
|
||||
from ipalib import util
|
||||
from ipalib.parameters import File, Flag, Str
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
class CertRetrieveOverride(MethodOverride):
|
||||
takes_options = (
|
||||
Str(
|
||||
'certificate_out?',
|
||||
doc=_('Write certificate (chain if --chain used) to file'),
|
||||
include='cli',
|
||||
cli_metavar='FILE',
|
||||
),
|
||||
)
|
||||
|
||||
def forward(self, *args, **options):
|
||||
if 'certificate_out' in options:
|
||||
certificate_out = options.pop('certificate_out')
|
||||
try:
|
||||
util.check_writable_file(certificate_out)
|
||||
except errors.FileError as e:
|
||||
raise errors.ValidationError(name='certificate-out',
|
||||
error=str(e))
|
||||
else:
|
||||
certificate_out = None
|
||||
|
||||
result = super(CertRetrieveOverride, self).forward(*args, **options)
|
||||
|
||||
if certificate_out is not None:
|
||||
if options.get('chain', False):
|
||||
certs = result['result']['certificate_chain']
|
||||
else:
|
||||
certs = [result['result']['certificate']]
|
||||
certs = (x509.load_der_x509_certificate(base64.b64decode(cert))
|
||||
for cert in certs)
|
||||
x509.write_certificate_list(certs, certificate_out)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class cert_request(CertRetrieveOverride):
|
||||
takes_options = CertRetrieveOverride.takes_options + (
|
||||
Str(
|
||||
'database?',
|
||||
label=_('Path to NSS database'),
|
||||
doc=_('Path to NSS database to use for private key'),
|
||||
),
|
||||
Str(
|
||||
'private_key?',
|
||||
label=_('Path to private key file'),
|
||||
doc=_('Path to PEM file containing a private key'),
|
||||
),
|
||||
Str(
|
||||
'password_file?',
|
||||
label=_(
|
||||
'File containing a password for the private key or database'),
|
||||
),
|
||||
Str(
|
||||
'csr_profile_id?',
|
||||
label=_('Name of CSR generation profile (if not the same as'
|
||||
' profile_id)'),
|
||||
),
|
||||
)
|
||||
|
||||
def get_args(self):
|
||||
for arg in super(cert_request, self).get_args():
|
||||
if arg.name == 'csr':
|
||||
arg = arg.clone_retype(arg.name, File, required=False)
|
||||
yield arg
|
||||
|
||||
def forward(self, csr=None, **options):
|
||||
database = options.pop('database', None)
|
||||
private_key = options.pop('private_key', None)
|
||||
csr_profile_id = options.pop('csr_profile_id', None)
|
||||
password_file = options.pop('password_file', None)
|
||||
|
||||
if csr is None:
|
||||
if database:
|
||||
adaptor = csrgen.NSSAdaptor(database, password_file)
|
||||
elif private_key:
|
||||
adaptor = csrgen.OpenSSLAdaptor(private_key, password_file)
|
||||
else:
|
||||
raise errors.InvocationError(
|
||||
message=u"One of 'database' or 'private_key' is required")
|
||||
|
||||
pubkey_info = adaptor.get_subject_public_key_info()
|
||||
pubkey_info_b64 = base64.b64encode(pubkey_info)
|
||||
|
||||
# If csr_profile_id is passed, that takes precedence.
|
||||
# Otherwise, use profile_id. If neither are passed, the default
|
||||
# in cert_get_requestdata will be used.
|
||||
profile_id = csr_profile_id
|
||||
if profile_id is None:
|
||||
profile_id = options.get('profile_id')
|
||||
|
||||
response = self.api.Command.cert_get_requestdata(
|
||||
profile_id=profile_id,
|
||||
principal=options.get('principal'),
|
||||
public_key_info=pubkey_info_b64)
|
||||
|
||||
req_info_b64 = response['result']['request_info']
|
||||
req_info = base64.b64decode(req_info_b64)
|
||||
|
||||
csr = adaptor.sign_csr(req_info)
|
||||
|
||||
if not csr:
|
||||
raise errors.CertificateOperationError(
|
||||
error=(_('Generated CSR was empty')))
|
||||
|
||||
else:
|
||||
if database is not None or private_key is not None:
|
||||
raise errors.MutuallyExclusiveError(reason=_(
|
||||
"Options 'database' and 'private_key' are not compatible"
|
||||
" with 'csr'"))
|
||||
|
||||
return super(cert_request, self).forward(csr, **options)
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class cert_show(CertRetrieveOverride):
|
||||
def get_options(self):
|
||||
for option in super(cert_show, self).get_options():
|
||||
if option.name == 'out':
|
||||
# skip server-defined --out
|
||||
continue
|
||||
if option.name == 'certificate_out':
|
||||
# add --out as a deprecated alias of --certificate-out
|
||||
option = option.clone_rename(
|
||||
'out',
|
||||
cli_name='certificate_out',
|
||||
deprecated_cli_aliases={'out'},
|
||||
)
|
||||
yield option
|
||||
|
||||
def forward(self, *args, **options):
|
||||
try:
|
||||
options['certificate_out'] = options.pop('out')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return super(cert_show, self).forward(*args, **options)
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class cert_remove_hold(MethodOverride):
|
||||
has_output_params = (
|
||||
Flag('unrevoked',
|
||||
label=_('Unrevoked'),
|
||||
),
|
||||
Str('error_string',
|
||||
label=_('Error'),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class cert_find(MethodOverride):
|
||||
takes_options = (
|
||||
File(
|
||||
'file?',
|
||||
label=_("Input filename"),
|
||||
doc=_('File to load the certificate from.'),
|
||||
include='cli',
|
||||
),
|
||||
)
|
||||
|
||||
def forward(self, *args, **options):
|
||||
if self.api.env.context == 'cli':
|
||||
if 'certificate' in options and 'file' in options:
|
||||
raise errors.MutuallyExclusiveError(
|
||||
reason=_("cannot specify both raw certificate and file"))
|
||||
if 'certificate' not in options and 'file' in options:
|
||||
options['certificate'] = x509.load_unknown_x509_certificate(
|
||||
options.pop('file'))
|
||||
|
||||
return super(cert_find, self).forward(*args, **options)
|
||||
49
ipaclient/plugins/certmap.py
Normal file
49
ipaclient/plugins/certmap.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import errors, x509
|
||||
from ipalib.parameters import File
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class certmap_match(MethodOverride):
|
||||
takes_args = (
|
||||
File(
|
||||
'file?',
|
||||
label=_("Input file"),
|
||||
doc=_("File to load the certificate from"),
|
||||
include='cli',
|
||||
),
|
||||
)
|
||||
|
||||
def get_args(self):
|
||||
for arg in super(certmap_match, self).get_args():
|
||||
if arg.name != 'certificate' or self.api.env.context != 'cli':
|
||||
yield arg
|
||||
|
||||
def get_options(self):
|
||||
for arg in super(certmap_match, self).get_args():
|
||||
if arg.name == 'certificate' and self.api.env.context == 'cli':
|
||||
yield arg.clone(required=False)
|
||||
for option in super(certmap_match, self).get_options():
|
||||
yield option
|
||||
|
||||
def forward(self, *args, **options):
|
||||
if self.api.env.context == 'cli':
|
||||
if args and 'certificate' in options:
|
||||
raise errors.MutuallyExclusiveError(
|
||||
reason=_("cannot specify both raw certificate and file"))
|
||||
if args:
|
||||
args = [x509.load_unknown_x509_certificate(args[0])]
|
||||
elif 'certificate' in options:
|
||||
args = [options.pop('certificate')]
|
||||
else:
|
||||
args = []
|
||||
|
||||
return super(certmap_match, self).forward(*args, **options)
|
||||
47
ipaclient/plugins/certprofile.py
Normal file
47
ipaclient/plugins/certprofile.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import util
|
||||
from ipalib.parameters import File
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=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
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class certprofile_import(MethodOverride):
|
||||
def get_options(self):
|
||||
for option in super(certprofile_import, self).get_options():
|
||||
if option.name == 'file':
|
||||
option = option.clone_retype(option.name, File)
|
||||
yield option
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class certprofile_mod(MethodOverride):
|
||||
def get_options(self):
|
||||
for option in super(certprofile_mod, self).get_options():
|
||||
if option.name == 'file':
|
||||
option = option.clone_retype(option.name, File)
|
||||
yield option
|
||||
130
ipaclient/plugins/csrgen.py
Normal file
130
ipaclient/plugins/csrgen.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import base64
|
||||
|
||||
import six
|
||||
|
||||
from ipalib import api
|
||||
from ipalib import errors
|
||||
from ipalib import output
|
||||
from ipalib import util
|
||||
from ipalib.frontend import Local, Str
|
||||
from ipalib.parameters import Bytes, Principal
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.text import _
|
||||
from ipapython import dogtag
|
||||
|
||||
try:
|
||||
import jinja2 # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
raise errors.SkipPluginModule(reason=_("jinja2 is not installed."))
|
||||
else:
|
||||
from ipaclient import csrgen
|
||||
from ipaclient import csrgen_ffi
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
register = Registry()
|
||||
|
||||
__doc__ = _("""
|
||||
Commands to build certificate requests automatically
|
||||
""")
|
||||
|
||||
|
||||
@register()
|
||||
class cert_get_requestdata(Local):
|
||||
__doc__ = _('Gather data for a certificate signing request.')
|
||||
|
||||
NO_CLI = True
|
||||
|
||||
takes_options = (
|
||||
Principal(
|
||||
'principal',
|
||||
label=_('Principal'),
|
||||
doc=_('Principal for this certificate (e.g.'
|
||||
' HTTP/test.example.com)'),
|
||||
),
|
||||
Str(
|
||||
'profile_id?',
|
||||
label=_('Profile ID'),
|
||||
doc=_('CSR Generation Profile to use'),
|
||||
),
|
||||
Bytes(
|
||||
'public_key_info',
|
||||
label=_('Subject Public Key Info'),
|
||||
doc=_('DER-encoded SubjectPublicKeyInfo structure'),
|
||||
),
|
||||
Str(
|
||||
'out?',
|
||||
doc=_('Write CertificationRequestInfo to file'),
|
||||
),
|
||||
)
|
||||
|
||||
has_output = (
|
||||
output.Output(
|
||||
'result',
|
||||
type=dict,
|
||||
doc=_('Dictionary mapping variable name to value'),
|
||||
),
|
||||
)
|
||||
|
||||
has_output_params = (
|
||||
Str(
|
||||
'request_info',
|
||||
label=_('CertificationRequestInfo structure'),
|
||||
)
|
||||
)
|
||||
|
||||
def execute(self, *args, **options):
|
||||
if 'out' in options:
|
||||
util.check_writable_file(options['out'])
|
||||
|
||||
principal = options.get('principal')
|
||||
profile_id = options.get('profile_id')
|
||||
if profile_id is None:
|
||||
profile_id = dogtag.DEFAULT_PROFILE
|
||||
public_key_info = options.get('public_key_info')
|
||||
public_key_info = base64.b64decode(public_key_info)
|
||||
|
||||
if self.api.env.in_server:
|
||||
backend = self.api.Backend.ldap2
|
||||
else:
|
||||
backend = self.api.Backend.rpcclient
|
||||
if not backend.isconnected():
|
||||
backend.connect()
|
||||
|
||||
try:
|
||||
if principal.is_host:
|
||||
principal_obj = api.Command.host_show(
|
||||
principal.hostname, all=True)
|
||||
elif principal.is_service:
|
||||
principal_obj = api.Command.service_show(
|
||||
unicode(principal), all=True)
|
||||
elif principal.is_user:
|
||||
principal_obj = api.Command.user_show(
|
||||
principal.username, all=True)
|
||||
except errors.NotFound:
|
||||
raise errors.NotFound(
|
||||
reason=_("The principal for this request doesn't exist."))
|
||||
principal_obj = principal_obj['result']
|
||||
config = api.Command.config_show()['result']
|
||||
|
||||
generator = csrgen.CSRGenerator(csrgen.FileRuleProvider())
|
||||
|
||||
csr_config = generator.csr_config(principal_obj, config, profile_id)
|
||||
request_info = base64.b64encode(csrgen_ffi.build_requestinfo(
|
||||
csr_config.encode('utf8'), public_key_info))
|
||||
|
||||
result = {}
|
||||
if 'out' in options:
|
||||
with open(options['out'], 'wb') as f:
|
||||
f.write(request_info)
|
||||
else:
|
||||
result = dict(request_info=request_info)
|
||||
|
||||
return dict(
|
||||
result=result
|
||||
)
|
||||
507
ipaclient/plugins/dns.py
Normal file
507
ipaclient/plugins/dns.py
Normal file
@@ -0,0 +1,507 @@
|
||||
# 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
|
||||
import copy
|
||||
import re
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import errors
|
||||
from ipalib.dns import (get_record_rrtype,
|
||||
has_cli_options,
|
||||
iterate_rrparams_by_parts,
|
||||
part_name_format,
|
||||
record_name_format)
|
||||
from ipalib.frontend import Command
|
||||
from ipalib.parameters import Bool, Str
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib import _, ngettext
|
||||
from ipalib import util
|
||||
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(rrtype, cmd, part, output_kw, default=None):
|
||||
name = part_name_format % (rrtype.lower(), part.name)
|
||||
label = unicode(cmd.params[name].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 % unicode(rrtype.lower())
|
||||
mod_parts = cmd.api.Command.dnsrecord_split_parts(
|
||||
name, mod_dnsvalue)['result']
|
||||
|
||||
user_options = {}
|
||||
try:
|
||||
rrobj = cmd.api.Object['dns{}record'.format(rrtype.lower())]
|
||||
except KeyError:
|
||||
return user_options
|
||||
|
||||
for part_id, part in enumerate(rrobj.params()):
|
||||
name = part_name_format % (rrtype.lower(), part.name)
|
||||
if name not in cmd.params:
|
||||
continue
|
||||
|
||||
if mod_parts:
|
||||
default = mod_parts[part_id]
|
||||
else:
|
||||
default = None
|
||||
|
||||
__get_part_param(rrtype, cmd, part, user_options, default)
|
||||
|
||||
return user_options
|
||||
|
||||
|
||||
def prompt_missing_parts(rrtype, cmd, kw, prompt_optional=False):
|
||||
user_options = {}
|
||||
try:
|
||||
rrobj = cmd.api.Object['dns{}record'.format(rrtype.lower())]
|
||||
except KeyError:
|
||||
return user_options
|
||||
|
||||
for part in rrobj.params():
|
||||
name = part_name_format % (rrtype.lower(), part.name)
|
||||
if name not in cmd.params:
|
||||
continue
|
||||
|
||||
if name in kw:
|
||||
continue
|
||||
|
||||
optional = not part.required
|
||||
if optional and not prompt_optional:
|
||||
continue
|
||||
|
||||
default = part.get_default(**kw)
|
||||
__get_part_param(rrtype, cmd, part, user_options, default)
|
||||
|
||||
return user_options
|
||||
|
||||
|
||||
class DNSZoneMethodOverride(MethodOverride):
|
||||
def get_options(self):
|
||||
for option in super(DNSZoneMethodOverride, self).get_options():
|
||||
if option.name == 'idnsallowdynupdate':
|
||||
option = option.clone_retype(option.name, Bool)
|
||||
yield option
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class dnszone_add(DNSZoneMethodOverride):
|
||||
pass
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class dnszone_mod(DNSZoneMethodOverride):
|
||||
pass
|
||||
|
||||
|
||||
# Support old servers without dnsrecord_split_parts
|
||||
# Do not add anything new here!
|
||||
@register(no_fail=True)
|
||||
class dnsrecord_split_parts(Command):
|
||||
NO_CLI = True
|
||||
|
||||
takes_args = (
|
||||
Str('name'),
|
||||
Str('value'),
|
||||
)
|
||||
|
||||
def execute(self, name, value, *args, **options):
|
||||
def split_exactly(count):
|
||||
values = value.split()
|
||||
if len(values) != count:
|
||||
return None
|
||||
return tuple(values)
|
||||
|
||||
result = ()
|
||||
|
||||
rrtype = get_record_rrtype(name)
|
||||
if rrtype in ('A', 'AAAA', 'CNAME', 'DNAME', 'NS', 'PTR'):
|
||||
result = split_exactly(1)
|
||||
elif rrtype in ('AFSDB', 'KX', 'MX'):
|
||||
result = split_exactly(2)
|
||||
elif rrtype in ('CERT', 'DLV', 'DS', 'SRV', 'TLSA'):
|
||||
result = split_exactly(4)
|
||||
elif rrtype in ('NAPTR'):
|
||||
result = split_exactly(6)
|
||||
elif rrtype in ('A6', 'TXT'):
|
||||
result = (value,)
|
||||
elif rrtype == 'LOC':
|
||||
regex = re.compile(
|
||||
r'(?P<d1>\d{1,2}\s+)'
|
||||
r'(?:(?P<m1>\d{1,2}\s+)'
|
||||
r'(?P<s1>\d{1,2}(?:\.\d{1,3})?\s+)?)?'
|
||||
r'(?P<dir1>[NS])\s+'
|
||||
r'(?P<d2>\d{1,3}\s+)'
|
||||
r'(?:(?P<m2>\d{1,2}\s+)'
|
||||
r'(?P<s2>\d{1,2}(?:\.\d{1,3})?\s+)?)?'
|
||||
r'(?P<dir2>[WE])\s+'
|
||||
r'(?P<alt>-?\d{1,8}(?:\.\d{1,2})?)m?'
|
||||
r'(?:\s+(?P<siz>\d{1,8}(?:\.\d{1,2})?)m?'
|
||||
r'(?:\s+(?P<hp>\d{1,8}(?:\.\d{1,2})?)m?'
|
||||
r'(?:\s+(?P<vp>\d{1,8}(?:\.\d{1,2})?)m?\s*)?)?)?$')
|
||||
|
||||
m = regex.match(value)
|
||||
if m is not None:
|
||||
result = tuple(
|
||||
x.strip() if x is not None else x for x in m.groups())
|
||||
elif rrtype == 'SSHFP':
|
||||
values = value.split(None, 2)
|
||||
if len(values) == 3:
|
||||
result = tuple(values)
|
||||
|
||||
return dict(result=result)
|
||||
|
||||
|
||||
@register(override=True, no_fail=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
|
||||
|
||||
rrtype = rrtype.upper()
|
||||
|
||||
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, no_fail=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, no_fail=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, no_fail=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, no_fail=True)
|
||||
class dnsforwardzone_add(MethodOverride):
|
||||
def interactive_prompt_callback(self, kw):
|
||||
if ('idnsforwarders' not in kw and
|
||||
kw.get('idnsforwardpolicy') != u'none'):
|
||||
kw['idnsforwarders'] = self.Backend.textui.prompt(
|
||||
_(u'DNS forwarder'))
|
||||
|
||||
# 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, no_fail=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 ..."))
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class dns_update_system_records(MethodOverride):
|
||||
record_groups = ('ipa_records', 'location_records')
|
||||
|
||||
takes_options = (
|
||||
Str(
|
||||
'out?',
|
||||
include='cli',
|
||||
doc=_('file to store DNS records in nsupdate format')
|
||||
),
|
||||
)
|
||||
def _standard_output(self, textui, result, labels):
|
||||
"""Print output in standard format common across the other plugins"""
|
||||
for key in self.record_groups:
|
||||
if result.get(key):
|
||||
textui.print_indented(u'{}:'.format(labels[key]), indent=1)
|
||||
for val in sorted(result[key]):
|
||||
textui.print_indented(val, indent=2)
|
||||
textui.print_line(u'')
|
||||
|
||||
def _nsupdate_output_file(self, out_f, result):
|
||||
"""Store data in nsupdate format in file"""
|
||||
def parse_rname_rtype(record):
|
||||
"""Get rname and rtype from textual representation of record"""
|
||||
l = record.split(' ', 4)
|
||||
return l[0], l[3]
|
||||
|
||||
labels = {
|
||||
p.name: unicode(p.label) for p in self.output_params()
|
||||
}
|
||||
|
||||
already_removed = set()
|
||||
for key in self.record_groups:
|
||||
if result.get(key): # process only non-empty
|
||||
out_f.write("; {}\n".format(labels[key])) # comment
|
||||
for val in sorted(result[key]):
|
||||
# delete old first
|
||||
r_name_type = parse_rname_rtype(val)
|
||||
if r_name_type not in already_removed:
|
||||
# remove it only once
|
||||
already_removed.add(r_name_type)
|
||||
out_f.write("update delete {rname} {rtype}\n".format(
|
||||
rname=r_name_type[0], rtype=r_name_type[1]
|
||||
))
|
||||
# add new
|
||||
out_f.write("update add {}\n".format(val))
|
||||
out_f.write("send\n\n")
|
||||
|
||||
def forward(self, *keys, **options):
|
||||
# pop `out` before sending to server as it is only client side option
|
||||
out = options.pop('out', None)
|
||||
if out:
|
||||
util.check_writable_file(out)
|
||||
|
||||
res = super(dns_update_system_records, self).forward(*keys, **options)
|
||||
|
||||
if out and 'result' in res:
|
||||
try:
|
||||
with open(out, "w") as f:
|
||||
self._nsupdate_output_file(f, res['result'])
|
||||
except (OSError, IOError) as e:
|
||||
raise errors.FileError(reason=unicode(e))
|
||||
|
||||
return res
|
||||
|
||||
def output_for_cli(self, textui, output, *args, **options):
|
||||
output_super = copy.deepcopy(output)
|
||||
super_res = output_super.get('result', {})
|
||||
super_res.pop('ipa_records', None)
|
||||
super_res.pop('location_records', None)
|
||||
|
||||
super(dns_update_system_records, self).output_for_cli(
|
||||
textui, output_super, *args, **options)
|
||||
|
||||
labels = {
|
||||
p.name: unicode(p.label) for p in self.output_params()
|
||||
}
|
||||
|
||||
result = output.get('result', {})
|
||||
|
||||
self._standard_output(textui, result, labels)
|
||||
|
||||
return int(not output['value'])
|
||||
45
ipaclient/plugins/hbacrule.py
Normal file
45
ipaclient/plugins/hbacrule.py
Normal file
@@ -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
|
||||
)
|
||||
)
|
||||
57
ipaclient/plugins/hbactest.py
Normal file
57
ipaclient/plugins/hbactest.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# 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, no_fail=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:
|
||||
if o == 'value':
|
||||
continue
|
||||
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):
|
||||
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'])
|
||||
48
ipaclient/plugins/host.py
Normal file
48
ipaclient/plugins/host.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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, no_fail=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']:
|
||||
certs = (x509.load_der_x509_certificate(c)
|
||||
for c in result['result']['usercertificate'])
|
||||
x509.write_certificate_list(certs, 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)
|
||||
89
ipaclient/plugins/idrange.py
Normal file
89
ipaclient/plugins/idrange.py
Normal file
@@ -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, no_fail=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')
|
||||
42
ipaclient/plugins/internal.py
Normal file
42
ipaclient/plugins/internal.py
Normal file
@@ -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, no_fail=True)
|
||||
class json_metadata(CommandOverride):
|
||||
def output_for_cli(self, textui, result, *args, **options):
|
||||
print(json.dumps(result, default=json_serialize))
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class i18n_messages(CommandOverride):
|
||||
def output_for_cli(self, textui, result, *args, **options):
|
||||
print(json.dumps(result, default=json_serialize))
|
||||
35
ipaclient/plugins/location.py
Normal file
35
ipaclient/plugins/location.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import _
|
||||
from ipalib.plugable import Registry
|
||||
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class location_show(MethodOverride):
|
||||
def output_for_cli(self, textui, output, *keys, **options):
|
||||
rv = super(location_show, self).output_for_cli(
|
||||
textui, output, *keys, **options)
|
||||
|
||||
servers = output.get('servers', {})
|
||||
first = True
|
||||
for details in servers.values():
|
||||
if first:
|
||||
textui.print_indented(_("Servers details:"), indent=1)
|
||||
first = False
|
||||
else:
|
||||
textui.print_line("")
|
||||
|
||||
for param in self.api.Command.server_find.output_params():
|
||||
if param.name in details:
|
||||
textui.print_indented(
|
||||
u"{}: {}".format(
|
||||
param.label, u', '.join(details[param.name])),
|
||||
indent=2)
|
||||
|
||||
return rv
|
||||
79
ipaclient/plugins/migration.py
Normal file
79
ipaclient/plugins/migration.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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.parameters import File
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib import _
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class migrate_ds(CommandOverride):
|
||||
migrate_order = ('user', 'group')
|
||||
|
||||
migration_disabled_msg = _('''\
|
||||
Migration mode is disabled.
|
||||
Use \'ipa config-mod --enable-migration=TRUE\' 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 get_options(self):
|
||||
for option in super(migrate_ds, self).get_options():
|
||||
if option.name == 'cacertfile':
|
||||
option = option.clone_retype(option.name, File)
|
||||
yield option
|
||||
|
||||
def output_for_cli(self, textui, result, ldapuri, **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))
|
||||
28
ipaclient/plugins/misc.py
Normal file
28
ipaclient/plugins/misc.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipalib.misc import env as _env
|
||||
from ipalib.misc import plugins as _plugins
|
||||
from ipalib.plugable import Registry
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class env(_env):
|
||||
def output_for_cli(self, textui, output, *args, **options):
|
||||
output = dict(output)
|
||||
output.pop('count', None)
|
||||
output.pop('total', None)
|
||||
options['all'] = True
|
||||
return super(env, self).output_for_cli(textui, output,
|
||||
*args, **options)
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class plugins(_plugins):
|
||||
def output_for_cli(self, textui, output, *args, **options):
|
||||
options['all'] = True
|
||||
return super(plugins, self).output_for_cli(textui, output,
|
||||
*args, **options)
|
||||
190
ipaclient/plugins/otptoken.py
Normal file
190
ipaclient/plugins/otptoken.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Authors:
|
||||
# Nathaniel McCallum <npmccallum@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 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 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 ipalib.util import create_https_connection
|
||||
from ipapython.dn import DN
|
||||
from ipapython.version import API_VERSION
|
||||
|
||||
import locale
|
||||
import qrcode
|
||||
|
||||
import six
|
||||
from six import StringIO
|
||||
from six.moves import urllib
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=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().encode(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 your 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."
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__kwargs = kwargs
|
||||
|
||||
# Can't use super() because the parent is an old-style class.
|
||||
urllib.request.HTTPSHandler.__init__(self)
|
||||
|
||||
def __inner(self, host, **kwargs):
|
||||
tmp = self.__kwargs.copy()
|
||||
tmp.update(kwargs)
|
||||
return create_https_connection(host, **tmp)
|
||||
|
||||
def https_open(self, req):
|
||||
# pylint: disable=no-member
|
||||
return self.do_open(self.__inner, req)
|
||||
|
||||
@register()
|
||||
class otptoken_sync(Local):
|
||||
__doc__ = _('Synchronize an OTP token.')
|
||||
|
||||
header = 'X-IPA-TokenSync-Result'
|
||||
|
||||
takes_options = (
|
||||
Str('user', label=_('User ID')),
|
||||
Password('password', label=_('Password'), confirm=False),
|
||||
Password('first_code', label=_('First Code'), confirm=False),
|
||||
Password('second_code', label=_('Second Code'), confirm=False),
|
||||
)
|
||||
|
||||
takes_args = (
|
||||
Str('token?', label=_('Token ID')),
|
||||
)
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
status = {'result': {self.header: 'unknown'}}
|
||||
|
||||
# Get the sync URI.
|
||||
segments = list(urllib.parse.urlparse(self.api.env.xmlrpc_uri))
|
||||
assert segments[0] == 'https' # Ensure encryption.
|
||||
segments[2] = segments[2].replace('/xml', '/session/sync_token')
|
||||
# urlunparse *can* take one argument
|
||||
# pylint: disable=too-many-function-args
|
||||
sync_uri = urllib.parse.urlunparse(segments)
|
||||
|
||||
# Prepare the query.
|
||||
query = {k: v for k, v in kwargs.items()
|
||||
if k in {x.name for x in self.takes_options}}
|
||||
if args and args[0] is not None:
|
||||
obj = self.api.Object.otptoken
|
||||
query['token'] = DN((obj.primary_key.name, args[0]),
|
||||
obj.container_dn, self.api.env.basedn)
|
||||
query = urllib.parse.urlencode(query)
|
||||
|
||||
# Sync the token.
|
||||
# pylint: disable=E1101
|
||||
handler = HTTPSHandler(
|
||||
cafile=api.env.tls_ca_cert,
|
||||
tls_version_min=api.env.tls_version_min,
|
||||
tls_version_max=api.env.tls_version_max)
|
||||
rsp = urllib.request.build_opener(handler).open(sync_uri, query)
|
||||
if rsp.getcode() == 200:
|
||||
status['result'][self.header] = rsp.info().get(self.header, 'unknown')
|
||||
rsp.close()
|
||||
|
||||
return status
|
||||
|
||||
def output_for_cli(self, textui, result, *keys, **options):
|
||||
textui.print_plain({
|
||||
'ok': 'Token synchronized.',
|
||||
'error': 'Error contacting server!',
|
||||
'invalid-credentials': 'Invalid Credentials!',
|
||||
}.get(result['result'][self.header], 'Unknown Error!'))
|
||||
192
ipaclient/plugins/otptoken_yubikey.py
Normal file
192
ipaclient/plugins/otptoken_yubikey.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# Authors:
|
||||
# Nathaniel McCallum <npmccallum@redhat.com>
|
||||
#
|
||||
# Copyright (C) 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/>.
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from ipalib import _, api, IntEnum
|
||||
from ipalib.errors import NotFound, SkipPluginModule
|
||||
from ipalib.frontend import Command, Method, Object
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.util import classproperty
|
||||
|
||||
try:
|
||||
import usb.core
|
||||
import yubico
|
||||
except ImportError:
|
||||
# python-yubico depends on pyusb
|
||||
raise SkipPluginModule(reason=_("python-yubico is not installed."))
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
__doc__ = _("""
|
||||
YubiKey Tokens
|
||||
""") + _("""
|
||||
Manage YubiKey tokens.
|
||||
""") + _("""
|
||||
This code is an extension to the otptoken plugin and provides support for
|
||||
reading/writing YubiKey tokens directly.
|
||||
""") + _("""
|
||||
EXAMPLES:
|
||||
""") + _("""
|
||||
Add a new token:
|
||||
ipa otptoken-add-yubikey --owner=jdoe --desc="My YubiKey"
|
||||
""")
|
||||
|
||||
register = Registry()
|
||||
|
||||
topic = 'otp'
|
||||
|
||||
|
||||
@register(no_fail=True)
|
||||
class _fake_otptoken(Object):
|
||||
name = 'otptoken'
|
||||
|
||||
|
||||
@register(no_fail=True)
|
||||
class _fake_otptoken_add(Method):
|
||||
name = 'otptoken_add'
|
||||
NO_CLI = True
|
||||
|
||||
|
||||
@register()
|
||||
class otptoken_add_yubikey(Command):
|
||||
__doc__ = _('Add a new YubiKey OTP token.')
|
||||
|
||||
takes_options = (
|
||||
IntEnum('slot?',
|
||||
cli_name='slot',
|
||||
label=_('YubiKey slot'),
|
||||
values=(1, 2),
|
||||
),
|
||||
)
|
||||
has_output_params = takes_options
|
||||
|
||||
@classmethod
|
||||
def __NO_CLI_getter(cls):
|
||||
return api.Command.get_plugin('otptoken_add') is _fake_otptoken_add
|
||||
|
||||
NO_CLI = classproperty(__NO_CLI_getter)
|
||||
|
||||
@property
|
||||
def api_version(self):
|
||||
return self.api.Command.otptoken_add.api_version
|
||||
|
||||
def get_args(self):
|
||||
for arg in self.api.Command.otptoken_add.args():
|
||||
yield arg
|
||||
for arg in super(otptoken_add_yubikey, self).get_args():
|
||||
yield arg
|
||||
|
||||
def get_options(self):
|
||||
for option in self.api.Command.otptoken_add.options():
|
||||
if option.name not in ('type',
|
||||
'ipatokenvendor',
|
||||
'ipatokenmodel',
|
||||
'ipatokenserial',
|
||||
'ipatokenotpalgorithm',
|
||||
'ipatokenhotpcounter',
|
||||
'ipatokenotpkey',
|
||||
'ipatokentotpclockoffset',
|
||||
'ipatokentotptimestep',
|
||||
'no_qrcode',
|
||||
'qrcode',
|
||||
'version'):
|
||||
yield option
|
||||
for option in super(otptoken_add_yubikey, self).get_options():
|
||||
yield option
|
||||
|
||||
def get_output_params(self):
|
||||
for param in self.api.Command.otptoken_add.output_params():
|
||||
yield param
|
||||
for param in super(otptoken_add_yubikey, self).get_output_params():
|
||||
yield param
|
||||
|
||||
def _iter_output(self):
|
||||
return self.api.Command.otptoken_add.output()
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
# Open the YubiKey
|
||||
try:
|
||||
yk = yubico.find_yubikey()
|
||||
except usb.core.USBError as e:
|
||||
raise NotFound(reason="No YubiKey found: %s" % e.strerror)
|
||||
except yubico.yubikey.YubiKeyError as e:
|
||||
raise NotFound(reason=e.reason)
|
||||
except ValueError as e:
|
||||
raise NotFound(reason=str(e) + ". Please install 'libyubikey' "
|
||||
"and 'libusb' packages first.")
|
||||
|
||||
assert yk.version_num() >= (2, 1)
|
||||
|
||||
# If no slot is specified, find the first free slot.
|
||||
if kwargs.get('slot', None) is None:
|
||||
try:
|
||||
used = yk.status().valid_configs()
|
||||
kwargs['slot'] = sorted({1, 2}.difference(used))[0]
|
||||
except IndexError:
|
||||
raise NotFound(reason=_('No free YubiKey slot!'))
|
||||
|
||||
# Create the key (NOTE: the length is fixed).
|
||||
key = os.urandom(20)
|
||||
|
||||
# Write the config.
|
||||
cfg = yk.init_config()
|
||||
cfg.mode_oath_hotp(key, kwargs.get(
|
||||
'ipatokenotpdigits',
|
||||
self.get_default_of('ipatokenotpdigits')
|
||||
))
|
||||
cfg.extended_flag('SERIAL_API_VISIBLE', True)
|
||||
yk.write_config(cfg, slot=kwargs['slot'])
|
||||
|
||||
# Filter the options we want to pass.
|
||||
options = {k: v for k, v in kwargs.items() if k in (
|
||||
'version',
|
||||
'description',
|
||||
'ipatokenowner',
|
||||
'ipatokendisabled',
|
||||
'ipatokennotbefore',
|
||||
'ipatokennotafter',
|
||||
'ipatokenotpdigits',
|
||||
)}
|
||||
|
||||
# Run the command.
|
||||
answer = self.Backend.rpcclient.forward('otptoken_add',
|
||||
*args,
|
||||
type=u'hotp',
|
||||
ipatokenvendor=u'YubiCo',
|
||||
ipatokenmodel=unicode(yk.model),
|
||||
ipatokenserial=unicode(yk.serial()),
|
||||
ipatokenotpalgorithm=u'sha1',
|
||||
ipatokenhotpcounter=0,
|
||||
ipatokenotpkey=key,
|
||||
no_qrcode=True,
|
||||
**options)
|
||||
|
||||
# Suppress values we don't want to return.
|
||||
for k in (u'uri', u'ipatokenotpkey'):
|
||||
if k in answer.get('result', {}):
|
||||
del answer['result'][k]
|
||||
|
||||
# Return which slot was used for writing.
|
||||
answer.get('result', {})['slot'] = kwargs['slot']
|
||||
|
||||
return answer
|
||||
17
ipaclient/plugins/passwd.py
Normal file
17
ipaclient/plugins/passwd.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import CommandOverride
|
||||
from ipalib.plugable import Registry
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class passwd(CommandOverride):
|
||||
def get_args(self):
|
||||
for arg in super(passwd, self).get_args():
|
||||
if arg.name == 'current_password':
|
||||
arg = arg.clone(sortorder=-1)
|
||||
yield arg
|
||||
31
ipaclient/plugins/permission.py
Normal file
31
ipaclient/plugins/permission.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib.plugable import Registry
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
class PermissionMethodOverride(MethodOverride):
|
||||
def get_options(self):
|
||||
for option in super(PermissionMethodOverride, self).get_options():
|
||||
if option.name == 'ipapermright':
|
||||
option = option.clone(deprecated_cli_aliases={'permissions'})
|
||||
yield option
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class permission_add(PermissionMethodOverride):
|
||||
pass
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class permission_mod(PermissionMethodOverride):
|
||||
pass
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class permission_find(PermissionMethodOverride):
|
||||
pass
|
||||
53
ipaclient/plugins/rpcclient.py
Normal file
53
ipaclient/plugins/rpcclient.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Authors:
|
||||
# Jason Gerard DeRose <jderose@redhat.com>
|
||||
# Rob Crittenden <rcritten@redhat.com>
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2008-2013 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/>.
|
||||
|
||||
"""
|
||||
RPC client plugins.
|
||||
"""
|
||||
|
||||
from ipalib import Registry, api
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
if 'in_server' in api.env and api.env.in_server is False:
|
||||
from ipalib.rpc import xmlclient, jsonclient
|
||||
register()(xmlclient)
|
||||
register()(jsonclient)
|
||||
|
||||
# FIXME: api.register only looks at the class name, so we need to create
|
||||
# trivial subclasses with the desired name.
|
||||
if api.env.rpc_protocol == 'xmlrpc':
|
||||
|
||||
class rpcclient(xmlclient):
|
||||
"""xmlclient renamed to 'rpcclient'"""
|
||||
pass
|
||||
register()(rpcclient)
|
||||
|
||||
elif api.env.rpc_protocol == 'jsonrpc':
|
||||
|
||||
class rpcclient(jsonclient):
|
||||
"""jsonclient renamed to 'rpcclient'"""
|
||||
pass
|
||||
register()(rpcclient)
|
||||
|
||||
else:
|
||||
raise ValueError('unknown rpc_protocol: %s' % api.env.rpc_protocol)
|
||||
21
ipaclient/plugins/server.py
Normal file
21
ipaclient/plugins/server.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipaclient.frontend import MethodOverride
|
||||
from ipalib import _, errors
|
||||
from ipalib.plugable import Registry
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register(override=True, no_fail=True)
|
||||
class server_del(MethodOverride):
|
||||
def interactive_prompt_callback(self, kw):
|
||||
server_list = kw.get('cn')
|
||||
if not server_list:
|
||||
raise errors.RequirementError(name='cn')
|
||||
|
||||
self.api.Backend.textui.print_plain(
|
||||
_("Removing %(servers)s from replication topology, "
|
||||
"please wait...") % {'servers': ', '.join(server_list)})
|
||||
50
ipaclient/plugins/service.py
Normal file
50
ipaclient/plugins/service.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# 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, no_fail=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']:
|
||||
certs = (x509.load_der_x509_certificate(c)
|
||||
for c in result['result']['usercertificate'])
|
||||
x509.write_certificate_list(certs, 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)
|
||||
57
ipaclient/plugins/sudorule.py
Normal file
57
ipaclient/plugins/sudorule.py
Normal file
@@ -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, no_fail=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, no_fail=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, no_fail=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, no_fail=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)
|
||||
53
ipaclient/plugins/topology.py
Normal file
53
ipaclient/plugins/topology.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#
|
||||
# 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, no_fail=True)
|
||||
class topologysuffix_verify(MethodOverride):
|
||||
def output_for_cli(self, textui, output, *args, **options):
|
||||
|
||||
connect_errors = output['result']['connect_errors']
|
||||
max_agmts_errors = output['result']['max_agmts_errors']
|
||||
|
||||
if not connect_errors and not max_agmts_errors:
|
||||
header = _('Replication topology of suffix "%(suffix)s" '
|
||||
'is in order.')
|
||||
textui.print_h1(header % {'suffix': args[0]})
|
||||
|
||||
if connect_errors:
|
||||
header = _('Replication topology of suffix "%(suffix)s" contains '
|
||||
'errors.')
|
||||
textui.print_h1(header % {'suffix': args[0]})
|
||||
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
|
||||
51
ipaclient/plugins/trust.py
Normal file
51
ipaclient/plugins/trust.py
Normal file
@@ -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, no_fail=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)
|
||||
81
ipaclient/plugins/user.py
Normal file
81
ipaclient/plugins/user.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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, no_fail=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, no_fail=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']:
|
||||
certs = (x509.load_der_x509_certificate(c)
|
||||
for c in result['result']['usercertificate'])
|
||||
x509.write_certificate_list(certs, 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)
|
||||
1140
ipaclient/plugins/vault.py
Normal file
1140
ipaclient/plugins/vault.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user