summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2016-04-28 10:30:05 +0200
committerJan Cholasta <jcholast@redhat.com>2016-06-03 09:00:34 +0200
commit6e44557b601f769d23ee74555a72e8b5cc62c0c9 (patch)
treeeedd3e054b0709341b9f58c190ea54f999f7d13a /ipalib
parentec841e5d7ab29d08de294b3fa863a631cd50e30a (diff)
downloadfreeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.gz
freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.xz
freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.zip
ipalib: move server-side plugins to ipaserver
Move the remaining plugin code from ipalib.plugins to ipaserver.plugins. Remove the now unused ipalib.plugins package. https://fedorahosted.org/freeipa/ticket/4739 Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/__init__.py2
-rw-r--r--ipalib/plugins/__init__.py22
-rw-r--r--ipalib/plugins/aci.py986
-rw-r--r--ipalib/plugins/automember.py802
-rw-r--r--ipalib/plugins/automount.py841
-rw-r--r--ipalib/plugins/baseldap.py2397
-rw-r--r--ipalib/plugins/baseuser.py663
-rw-r--r--ipalib/plugins/batch.py143
-rw-r--r--ipalib/plugins/caacl.py562
-rw-r--r--ipalib/plugins/cert.py835
-rw-r--r--ipalib/plugins/certprofile.py335
-rw-r--r--ipalib/plugins/config.py358
-rw-r--r--ipalib/plugins/delegation.py226
-rw-r--r--ipalib/plugins/dns.py4396
-rw-r--r--ipalib/plugins/domainlevel.py137
-rw-r--r--ipalib/plugins/group.py690
-rw-r--r--ipalib/plugins/hbac.py7
-rw-r--r--ipalib/plugins/hbacrule.py605
-rw-r--r--ipalib/plugins/hbacsvc.py152
-rw-r--r--ipalib/plugins/hbacsvcgroup.py176
-rw-r--r--ipalib/plugins/hbactest.py499
-rw-r--r--ipalib/plugins/host.py1284
-rw-r--r--ipalib/plugins/hostgroup.py316
-rw-r--r--ipalib/plugins/idrange.py769
-rw-r--r--ipalib/plugins/idviews.py1123
-rw-r--r--ipalib/plugins/internal.py859
-rw-r--r--ipalib/plugins/krbtpolicy.py243
-rw-r--r--ipalib/plugins/migration.py920
-rw-r--r--ipalib/plugins/misc.py138
-rw-r--r--ipalib/plugins/netgroup.py387
-rw-r--r--ipalib/plugins/otp.py7
-rw-r--r--ipalib/plugins/otpconfig.py121
-rw-r--r--ipalib/plugins/otptoken.py464
-rw-r--r--ipalib/plugins/passwd.py139
-rw-r--r--ipalib/plugins/permission.py1395
-rw-r--r--ipalib/plugins/ping.py70
-rw-r--r--ipalib/plugins/pkinit.py105
-rw-r--r--ipalib/plugins/privilege.py251
-rw-r--r--ipalib/plugins/pwpolicy.py611
-rw-r--r--ipalib/plugins/radiusproxy.py175
-rw-r--r--ipalib/plugins/realmdomains.py340
-rw-r--r--ipalib/plugins/role.py252
-rw-r--r--ipalib/plugins/schema.py660
-rw-r--r--ipalib/plugins/selfservice.py224
-rw-r--r--ipalib/plugins/selinuxusermap.py569
-rw-r--r--ipalib/plugins/server.py260
-rw-r--r--ipalib/plugins/service.py889
-rw-r--r--ipalib/plugins/servicedelegation.py550
-rw-r--r--ipalib/plugins/session.py33
-rw-r--r--ipalib/plugins/stageuser.py745
-rw-r--r--ipalib/plugins/sudo.py7
-rw-r--r--ipalib/plugins/sudocmd.py203
-rw-r--r--ipalib/plugins/sudocmdgroup.py195
-rw-r--r--ipalib/plugins/sudorule.py998
-rw-r--r--ipalib/plugins/topology.py503
-rw-r--r--ipalib/plugins/trust.py1725
-rw-r--r--ipalib/plugins/user.py1151
-rw-r--r--ipalib/plugins/vault.py1215
-rw-r--r--ipalib/plugins/virtual.py68
-rw-r--r--ipalib/setup.py.in4
60 files changed, 1 insertions, 34801 deletions
diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index 0070e62db..4a61aced2 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -908,10 +908,8 @@ class API(plugable.API):
@property
def packages(self):
if self.env.in_server:
- import ipalib.plugins
import ipaserver.plugins
result = (
- ipalib.plugins,
ipaserver.plugins,
)
else:
diff --git a/ipalib/plugins/__init__.py b/ipalib/plugins/__init__.py
deleted file mode 100644
index e3bf6db35..000000000
--- a/ipalib/plugins/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Authors:
-# Jason Gerard DeRose <jderose@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, 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/>.
-
-"""
-Sub-package containing all core plugins.
-"""
diff --git a/ipalib/plugins/aci.py b/ipalib/plugins/aci.py
deleted file mode 100644
index 01c929230..000000000
--- a/ipalib/plugins/aci.py
+++ /dev/null
@@ -1,986 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# 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/>.
-"""
-Directory Server Access Control Instructions (ACIs)
-
-ACIs are used to allow or deny access to information. This module is
-currently designed to allow, not deny, access.
-
-The aci commands are designed to grant permissions that allow updating
-existing entries or adding or deleting new ones. The goal of the ACIs
-that ship with IPA is to provide a set of low-level permissions that
-grant access to special groups called taskgroups. These low-level
-permissions can be combined into roles that grant broader access. These
-roles are another type of group, roles.
-
-For example, if you have taskgroups that allow adding and modifying users you
-could create a role, useradmin. You would assign users to the useradmin
-role to allow them to do the operations defined by the taskgroups.
-
-You can create ACIs that delegate permission so users in group A can write
-attributes on group B.
-
-The type option is a map that applies to all entries in the users, groups or
-host location. It is primarily designed to be used when granting add
-permissions (to write new entries).
-
-An ACI consists of three parts:
-1. target
-2. permissions
-3. bind rules
-
-The target is a set of rules that define which LDAP objects are being
-targeted. This can include a list of attributes, an area of that LDAP
-tree or an LDAP filter.
-
-The targets include:
-- attrs: list of attributes affected
-- type: an object type (user, group, host, service, etc)
-- memberof: members of a group
-- targetgroup: grant access to modify a specific group. This is primarily
- designed to enable users to add or remove members of a specific group.
-- filter: A legal LDAP filter used to narrow the scope of the target.
-- subtree: Used to apply a rule across an entire set of objects. For example,
- to allow adding users you need to grant "add" permission to the subtree
- ldap://uid=*,cn=users,cn=accounts,dc=example,dc=com. The subtree option
- is a fail-safe for objects that may not be covered by the type option.
-
-The permissions define what the ACI is allowed to do, and are one or
-more of:
-1. write - write one or more attributes
-2. read - read one or more attributes
-3. add - add a new entry to the tree
-4. delete - delete an existing entry
-5. all - all permissions are granted
-
-Note the distinction between attributes and entries. The permissions are
-independent, so being able to add a user does not mean that the user will
-be editable.
-
-The bind rule defines who this ACI grants permissions to. The LDAP server
-allows this to be any valid LDAP entry but we encourage the use of
-taskgroups so that the rights can be easily shared through roles.
-
-For a more thorough description of access controls see
-http://www.redhat.com/docs/manuals/dir-server/ag/8.0/Managing_Access_Control.html
-
-EXAMPLES:
-
-NOTE: ACIs are now added via the permission plugin. These examples are to
-demonstrate how the various options work but this is done via the permission
-command-line now (see last example).
-
- Add an ACI so that the group "secretaries" can update the address on any user:
- ipa group-add --desc="Office secretaries" secretaries
- ipa aci-add --attrs=streetAddress --memberof=ipausers --group=secretaries --permissions=write --prefix=none "Secretaries write addresses"
-
- Show the new ACI:
- ipa aci-show --prefix=none "Secretaries write addresses"
-
- Add an ACI that allows members of the "addusers" permission to add new users:
- ipa aci-add --type=user --permission=addusers --permissions=add --prefix=none "Add new users"
-
- Add an ACI that allows members of the editors manage members of the admins group:
- ipa aci-add --permissions=write --attrs=member --targetgroup=admins --group=editors --prefix=none "Editors manage admins"
-
- Add an ACI that allows members of the admins group to manage the street and zip code of those in the editors group:
- ipa aci-add --permissions=write --memberof=editors --group=admins --attrs=street --attrs=postalcode --prefix=none "admins edit the address of editors"
-
- Add an ACI that allows the admins group manage the street and zipcode of those who work for the boss:
- ipa aci-add --permissions=write --group=admins --attrs=street --attrs=postalcode --filter="(manager=uid=boss,cn=users,cn=accounts,dc=example,dc=com)" --prefix=none "Edit the address of those who work for the boss"
-
- Add an entirely new kind of record to IPA that isn't covered by any of the --type options, creating a permission:
- ipa permission-add --permissions=add --subtree="cn=*,cn=orange,cn=accounts,dc=example,dc=com" --desc="Add Orange Entries" add_orange
-
-
-The show command shows the raw 389-ds ACI.
-
-IMPORTANT: When modifying the target attributes of an existing ACI you
-must include all existing attributes as well. When doing an aci-mod the
-targetattr REPLACES the current attributes, it does not add to them.
-
-"""
-from copy import deepcopy
-
-import six
-
-from ipalib import api, crud, errors
-from ipalib import Object
-from ipalib import Flag, Str, StrEnum, DNParam
-from ipalib.aci import ACI
-from ipalib import output
-from ipalib import _, ngettext
-from ipalib.plugable import Registry
-from .baseldap import gen_pkey_only_option, pkey_to_value
-from ipapython.ipa_log_manager import root_logger
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-register = Registry()
-
-ACI_NAME_PREFIX_SEP = ":"
-
-_type_map = {
- 'user': 'ldap:///' + str(DN(('uid', '*'), api.env.container_user, api.env.basedn)),
- 'group': 'ldap:///' + str(DN(('cn', '*'), api.env.container_group, api.env.basedn)),
- 'host': 'ldap:///' + str(DN(('fqdn', '*'), api.env.container_host, api.env.basedn)),
- 'hostgroup': 'ldap:///' + str(DN(('cn', '*'), api.env.container_hostgroup, api.env.basedn)),
- 'service': 'ldap:///' + str(DN(('krbprincipalname', '*'), api.env.container_service, api.env.basedn)),
- 'netgroup': 'ldap:///' + str(DN(('ipauniqueid', '*'), api.env.container_netgroup, api.env.basedn)),
- 'dnsrecord': 'ldap:///' + str(DN(('idnsname', '*'), api.env.container_dns, api.env.basedn)),
-}
-
-_valid_permissions_values = [
- u'read', u'write', u'add', u'delete', u'all'
-]
-
-_valid_prefix_values = (
- u'permission', u'delegation', u'selfservice', u'none'
-)
-
-class ListOfACI(output.Output):
- type = (list, tuple)
- doc = _('A list of ACI values')
-
- def validate(self, cmd, entries):
- assert isinstance(entries, self.type)
- for (i, entry) in enumerate(entries):
- if not isinstance(entry, unicode):
- raise TypeError(output.emsg %
- (cmd.name, self.__class__.__name__,
- self.name, i, unicode, type(entry), entry)
- )
-
-aci_output = (
- output.Output('result', unicode, 'A string representing the ACI'),
- output.value,
- output.summary,
-)
-
-
-def _make_aci_name(aciprefix, aciname):
- """
- Given a name and a prefix construct an ACI name.
- """
- if aciprefix == u"none":
- return aciname
-
- return aciprefix + ACI_NAME_PREFIX_SEP + aciname
-
-def _parse_aci_name(aciname):
- """
- Parse the raw ACI name and return a tuple containing the ACI prefix
- and the actual ACI name.
- """
- aciparts = aciname.partition(ACI_NAME_PREFIX_SEP)
-
- if not aciparts[2]: # no prefix/name separator found
- return (u"none",aciparts[0])
-
- return (aciparts[0], aciparts[2])
-
-def _group_from_memberof(memberof):
- """
- Pull the group name out of a memberOf filter
- """
- st = memberof.find('memberOf=')
- if st == -1:
- # We have a raw group name, use that
- return api.Object['group'].get_dn(memberof)
- en = memberof.find(')', st)
- return memberof[st+9:en]
-
-def _make_aci(ldap, current, aciname, kw):
- """
- Given a name and a set of keywords construct an ACI.
- """
- # Do some quick and dirty validation.
- checked_args=['type','filter','subtree','targetgroup','attrs','memberof']
- valid={}
- for arg in checked_args:
- if arg in kw:
- valid[arg]=kw[arg] is not None
- else:
- valid[arg]=False
-
- if valid['type'] + valid['filter'] + valid['subtree'] + valid['targetgroup'] > 1:
- raise errors.ValidationError(name='target', error=_('type, filter, subtree and targetgroup are mutually exclusive'))
-
- if 'aciprefix' not in kw:
- raise errors.ValidationError(name='aciprefix', error=_('ACI prefix is required'))
-
- if sum(valid.values()) == 0:
- raise errors.ValidationError(name='target', error=_('at least one of: type, filter, subtree, targetgroup, attrs or memberof are required'))
-
- if valid['filter'] + valid['memberof'] > 1:
- raise errors.ValidationError(name='target', error=_('filter and memberof are mutually exclusive'))
-
- group = 'group' in kw
- permission = 'permission' in kw
- selfaci = 'selfaci' in kw and kw['selfaci'] == True
- if group + permission + selfaci > 1:
- raise errors.ValidationError(name='target', error=_('group, permission and self are mutually exclusive'))
- elif group + permission + selfaci == 0:
- raise errors.ValidationError(name='target', error=_('One of group, permission or self is required'))
-
- # Grab the dn of the group we're granting access to. This group may be a
- # permission or a user group.
- entry_attrs = []
- if permission:
- # This will raise NotFound if the permission doesn't exist
- try:
- entry_attrs = api.Command['permission_show'](kw['permission'])['result']
- except errors.NotFound as e:
- if 'test' in kw and not kw.get('test'):
- raise e
- else:
- entry_attrs = {
- 'dn': DN(('cn', kw['permission']),
- api.env.container_permission, api.env.basedn),
- }
- elif group:
- # Not so friendly with groups. This will raise
- try:
- group_dn = api.Object['group'].get_dn_if_exists(kw['group'])
- entry_attrs = {'dn': group_dn}
- except errors.NotFound:
- raise errors.NotFound(reason=_("Group '%s' does not exist") % kw['group'])
-
- try:
- a = ACI(current)
- a.name = _make_aci_name(kw['aciprefix'], aciname)
- a.permissions = kw['permissions']
- if 'selfaci' in kw and kw['selfaci']:
- a.set_bindrule('userdn = "ldap:///self"')
- else:
- dn = entry_attrs['dn']
- a.set_bindrule('groupdn = "ldap:///%s"' % dn)
- if valid['attrs']:
- a.set_target_attr(kw['attrs'])
- if valid['memberof']:
- try:
- api.Object['group'].get_dn_if_exists(kw['memberof'])
- except errors.NotFound:
- api.Object['group'].handle_not_found(kw['memberof'])
- groupdn = _group_from_memberof(kw['memberof'])
- a.set_target_filter('memberOf=%s' % groupdn)
- if valid['filter']:
- # Test the filter by performing a simple search on it. The
- # filter is considered valid if either it returns some entries
- # or it returns no entries, otherwise we let whatever exception
- # happened be raised.
- if kw['filter'] in ('', None, u''):
- raise errors.BadSearchFilter(info=_('empty filter'))
- try:
- entries = ldap.find_entries(filter=kw['filter'])
- except errors.NotFound:
- pass
- a.set_target_filter(kw['filter'])
- if valid['type']:
- target = _type_map[kw['type']]
- a.set_target(target)
- if valid['targetgroup']:
- # Purposely no try here so we'll raise a NotFound
- group_dn = api.Object['group'].get_dn_if_exists(kw['targetgroup'])
- target = 'ldap:///%s' % group_dn
- a.set_target(target)
- if valid['subtree']:
- # See if the subtree is a full URI
- target = kw['subtree']
- if not target.startswith('ldap:///'):
- target = 'ldap:///%s' % target
- a.set_target(target)
- except SyntaxError as e:
- raise errors.ValidationError(name='target', error=_('Syntax Error: %(error)s') % dict(error=str(e)))
-
- return a
-
-def _aci_to_kw(ldap, a, test=False, pkey_only=False):
- """Convert an ACI into its equivalent keywords.
-
- This is used for the modify operation so we can merge the
- incoming kw and existing ACI and pass the result to
- _make_aci().
- """
- kw = {}
- kw['aciprefix'], kw['aciname'] = _parse_aci_name(a.name)
- if pkey_only:
- return kw
- kw['permissions'] = tuple(a.permissions)
- if 'targetattr' in a.target:
- kw['attrs'] = tuple(unicode(e)
- for e in a.target['targetattr']['expression'])
- if 'targetfilter' in a.target:
- target = a.target['targetfilter']['expression']
- if target.startswith('(memberOf=') or target.startswith('memberOf='):
- (junk, memberof) = target.split('memberOf=', 1)
- memberof = DN(memberof)
- kw['memberof'] = memberof['cn']
- else:
- kw['filter'] = unicode(target)
- if 'target' in a.target:
- target = a.target['target']['expression']
- found = False
- for k in _type_map.keys():
- if _type_map[k] == target:
- kw['type'] = unicode(k)
- found = True
- break
- if not found:
- if target.startswith('('):
- kw['filter'] = unicode(target)
- else:
- # See if the target is a group. If so we set the
- # targetgroup attr, otherwise we consider it a subtree
- try:
- targetdn = DN(target.replace('ldap:///',''))
- except ValueError as e:
- raise errors.ValidationError(name='subtree', error=_("invalid DN (%s)") % e.message)
- if targetdn.endswith(DN(api.env.container_group, api.env.basedn)):
- kw['targetgroup'] = targetdn[0]['cn']
- else:
- kw['subtree'] = unicode(target)
-
- groupdn = a.bindrule['expression']
- groupdn = groupdn.replace('ldap:///','')
- if groupdn == 'self':
- kw['selfaci'] = True
- elif groupdn == 'anyone':
- pass
- else:
- groupdn = DN(groupdn)
- if len(groupdn) and groupdn[0].attr == 'cn':
- dn = DN()
- entry = ldap.make_entry(dn)
- try:
- entry = ldap.get_entry(groupdn, ['cn'])
- except errors.NotFound as e:
- # FIXME, use real name here
- if test:
- dn = DN(('cn', 'test'), api.env.container_permission,
- api.env.basedn)
- entry = ldap.make_entry(dn, {'cn': [u'test']})
- if api.env.container_permission in entry.dn:
- kw['permission'] = entry['cn'][0]
- else:
- if 'cn' in entry:
- kw['group'] = entry['cn'][0]
-
- return kw
-
-def _convert_strings_to_acis(acistrs):
- acis = []
- for a in acistrs:
- try:
- acis.append(ACI(a))
- except SyntaxError as e:
- root_logger.warning("Failed to parse: %s" % a)
- return acis
-
-def _find_aci_by_name(acis, aciprefix, aciname):
- name = _make_aci_name(aciprefix, aciname).lower()
- for a in acis:
- if a.name.lower() == name:
- return a
- raise errors.NotFound(reason=_('ACI with name "%s" not found') % aciname)
-
-
-def validate_permissions(ugettext, perm):
- perm = perm.strip().lower()
- if perm not in _valid_permissions_values:
- return '"%s" is not a valid permission' % perm
-
-
-def _normalize_permissions(perm):
- valid_permissions = []
- perm = perm.strip().lower()
- if perm not in valid_permissions:
- valid_permissions.append(perm)
- return ','.join(valid_permissions)
-
-_prefix_option = StrEnum('aciprefix',
- cli_name='prefix',
- label=_('ACI prefix'),
- doc=_('Prefix used to distinguish ACI types ' \
- '(permission, delegation, selfservice, none)'),
- values=_valid_prefix_values,
- )
-
-
-@register()
-class aci(Object):
- """
- ACI object.
- """
- NO_CLI = True
-
- label = _('ACIs')
-
- takes_params = (
- Str('aciname',
- cli_name='name',
- label=_('ACI name'),
- primary_key=True,
- flags=('virtual_attribute',),
- ),
- Str('permission?',
- cli_name='permission',
- label=_('Permission'),
- doc=_('Permission ACI grants access to'),
- flags=('virtual_attribute',),
- ),
- Str('group?',
- cli_name='group',
- label=_('User group'),
- doc=_('User group ACI grants access to'),
- flags=('virtual_attribute',),
- ),
- Str('permissions+', validate_permissions,
- cli_name='permissions',
- label=_('Permissions'),
- doc=_('Permissions to grant' \
- '(read, write, add, delete, all)'),
- normalizer=_normalize_permissions,
- flags=('virtual_attribute',),
- ),
- Str('attrs*',
- cli_name='attrs',
- label=_('Attributes to which the permission applies'),
- doc=_('Attributes'),
- flags=('virtual_attribute',),
- ),
- StrEnum('type?',
- cli_name='type',
- label=_('Type'),
- doc=_('type of IPA object (user, group, host, hostgroup, service, netgroup)'),
- values=(u'user', u'group', u'host', u'service', u'hostgroup', u'netgroup', u'dnsrecord'),
- flags=('virtual_attribute',),
- ),
- Str('memberof?',
- cli_name='memberof',
- label=_('Member of'), # FIXME: Does this label make sense?
- doc=_('Member of a group'),
- flags=('virtual_attribute',),
- ),
- Str('filter?',
- cli_name='filter',
- label=_('Filter'),
- doc=_('Legal LDAP filter (e.g. ou=Engineering)'),
- flags=('virtual_attribute',),
- ),
- Str('subtree?',
- cli_name='subtree',
- label=_('Subtree'),
- doc=_('Subtree to apply ACI to'),
- flags=('virtual_attribute',),
- ),
- Str('targetgroup?',
- cli_name='targetgroup',
- label=_('Target group'),
- doc=_('Group to apply ACI to'),
- flags=('virtual_attribute',),
- ),
- Flag('selfaci?',
- cli_name='self',
- label=_('Target your own entry (self)'),
- doc=_('Apply ACI to your own entry (self)'),
- flags=('virtual_attribute',),
- ),
- )
-
-
-@register()
-class aci_add(crud.Create):
- """
- Create new ACI.
- """
- NO_CLI = True
- msg_summary = _('Created ACI "%(value)s"')
-
- takes_options = (
- _prefix_option,
- Flag('test?',
- doc=_('Test the ACI syntax but don\'t write anything'),
- default=False,
- ),
- )
-
- def execute(self, aciname, **kw):
- """
- Execute the aci-create operation.
-
- Returns the entry as it will be created in LDAP.
-
- :param aciname: The name of the ACI being added.
- :param kw: Keyword arguments for the other LDAP attributes.
- """
- assert 'aciname' not in kw
- ldap = self.api.Backend.ldap2
-
- newaci = _make_aci(ldap, None, aciname, kw)
-
- entry = ldap.get_entry(self.api.env.basedn, ['aci'])
-
- acis = _convert_strings_to_acis(entry.get('aci', []))
- for a in acis:
- # FIXME: add check for permission_group = permission_group
- if a.isequal(newaci) or newaci.name == a.name:
- raise errors.DuplicateEntry()
-
- newaci_str = unicode(newaci)
- entry.setdefault('aci', []).append(newaci_str)
-
- if not kw.get('test', False):
- ldap.update_entry(entry)
-
- if kw.get('raw', False):
- result = dict(aci=unicode(newaci_str))
- else:
- result = _aci_to_kw(ldap, newaci, kw.get('test', False))
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-@register()
-class aci_del(crud.Delete):
- """
- Delete ACI.
- """
- NO_CLI = True
- has_output = output.standard_boolean
- msg_summary = _('Deleted ACI "%(value)s"')
-
- takes_options = (_prefix_option,)
-
- def execute(self, aciname, aciprefix, **options):
- """
- Execute the aci-delete operation.
-
- :param aciname: The name of the ACI being deleted.
- :param aciprefix: The ACI prefix.
- """
- ldap = self.api.Backend.ldap2
-
- entry = ldap.get_entry(self.api.env.basedn, ['aci'])
-
- acistrs = entry.get('aci', [])
- acis = _convert_strings_to_acis(acistrs)
- aci = _find_aci_by_name(acis, aciprefix, aciname)
- for a in acistrs:
- candidate = ACI(a)
- if aci.isequal(candidate):
- acistrs.remove(a)
- break
-
- entry['aci'] = acistrs
-
- ldap.update_entry(entry)
-
- return dict(
- result=True,
- value=pkey_to_value(aciname, options),
- )
-
-
-@register()
-class aci_mod(crud.Update):
- """
- Modify ACI.
- """
- NO_CLI = True
- has_output_params = (
- Str('aci',
- label=_('ACI'),
- ),
- )
-
- takes_options = (_prefix_option,)
-
- internal_options = ['rename']
-
- msg_summary = _('Modified ACI "%(value)s"')
-
- def execute(self, aciname, **kw):
- aciprefix = kw['aciprefix']
- ldap = self.api.Backend.ldap2
-
- entry = ldap.get_entry(self.api.env.basedn, ['aci'])
-
- acis = _convert_strings_to_acis(entry.get('aci', []))
- aci = _find_aci_by_name(acis, aciprefix, aciname)
-
- # The strategy here is to convert the ACI we're updating back into
- # a series of keywords. Then we replace any keywords that have been
- # updated and convert that back into an ACI and write it out.
- oldkw = _aci_to_kw(ldap, aci)
- newkw = deepcopy(oldkw)
- if newkw.get('selfaci', False):
- # selfaci is set in aci_to_kw to True only if the target is self
- kw['selfaci'] = True
- newkw.update(kw)
- for acikw in (oldkw, newkw):
- acikw.pop('aciname', None)
-
- # _make_aci is what is run in aci_add and validates the input.
- # Do this before we delete the existing ACI.
- newaci = _make_aci(ldap, None, aciname, newkw)
- if aci.isequal(newaci):
- raise errors.EmptyModlist()
-
- self.api.Command['aci_del'](aciname, aciprefix=aciprefix)
-
- try:
- result = self.api.Command['aci_add'](aciname, **newkw)['result']
- except Exception as e:
- # ACI could not be added, try to restore the old deleted ACI and
- # report the ADD error back to user
- try:
- self.api.Command['aci_add'](aciname, **oldkw)
- except Exception:
- pass
- raise e
-
- if kw.get('raw', False):
- result = dict(aci=unicode(newaci))
- else:
- result = _aci_to_kw(ldap, newaci)
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-@register()
-class aci_find(crud.Search):
- """
- Search for ACIs.
-
- Returns a list of ACIs
-
- EXAMPLES:
-
- To find all ACIs that apply directly to members of the group ipausers:
- ipa aci-find --memberof=ipausers
-
- To find all ACIs that grant add access:
- ipa aci-find --permissions=add
-
- Note that the find command only looks for the given text in the set of
- ACIs, it does not evaluate the ACIs to see if something would apply.
- For example, searching on memberof=ipausers will find all ACIs that
- have ipausers as a memberof. There may be other ACIs that apply to
- members of that group indirectly.
- """
- NO_CLI = True
- msg_summary = ngettext('%(count)d ACI matched', '%(count)d ACIs matched', 0)
-
- takes_options = (_prefix_option.clone_rename("aciprefix?", required=False),
- gen_pkey_only_option("name"),)
-
- def execute(self, term=None, **kw):
- ldap = self.api.Backend.ldap2
-
- entry = ldap.get_entry(self.api.env.basedn, ['aci'])
-
- acis = _convert_strings_to_acis(entry.get('aci', []))
- results = []
-
- if term:
- term = term.lower()
- for a in acis:
- if a.name.lower().find(term) != -1 and a not in results:
- results.append(a)
- acis = list(results)
- else:
- results = list(acis)
-
- if kw.get('aciname'):
- for a in acis:
- prefix, name = _parse_aci_name(a.name)
- if name != kw['aciname']:
- results.remove(a)
- acis = list(results)
-
- if kw.get('aciprefix'):
- for a in acis:
- prefix, name = _parse_aci_name(a.name)
- if prefix != kw['aciprefix']:
- results.remove(a)
- acis = list(results)
-
- if kw.get('attrs'):
- for a in acis:
- if not 'targetattr' in a.target:
- results.remove(a)
- continue
- alist1 = sorted(
- [t.lower() for t in a.target['targetattr']['expression']]
- )
- alist2 = sorted([t.lower() for t in kw['attrs']])
- if len(set(alist1) & set(alist2)) != len(alist2):
- results.remove(a)
- acis = list(results)
-
- if kw.get('permission'):
- try:
- self.api.Command['permission_show'](
- kw['permission']
- )
- except errors.NotFound:
- pass
- else:
- for a in acis:
- uri = 'ldap:///%s' % entry.dn
- if a.bindrule['expression'] != uri:
- results.remove(a)
- acis = list(results)
-
- if kw.get('permissions'):
- for a in acis:
- alist1 = sorted(a.permissions)
- alist2 = sorted(kw['permissions'])
- if len(set(alist1) & set(alist2)) != len(alist2):
- results.remove(a)
- acis = list(results)
-
- if kw.get('memberof'):
- try:
- dn = _group_from_memberof(kw['memberof'])
- except errors.NotFound:
- pass
- else:
- memberof_filter = '(memberOf=%s)' % dn
- for a in acis:
- if 'targetfilter' in a.target:
- targetfilter = a.target['targetfilter']['expression']
- if targetfilter != memberof_filter:
- results.remove(a)
- else:
- results.remove(a)
-
- if kw.get('type'):
- for a in acis:
- if 'target' in a.target:
- target = a.target['target']['expression']
- else:
- results.remove(a)
- continue
- found = False
- for k in _type_map.keys():
- if _type_map[k] == target and kw['type'] == k:
- found = True
- break
- if not found:
- try:
- results.remove(a)
- except ValueError:
- pass
-
- if kw.get('selfaci', False) is True:
- for a in acis:
- if a.bindrule['expression'] != u'ldap:///self':
- try:
- results.remove(a)
- except ValueError:
- pass
-
- if kw.get('group'):
- for a in acis:
- groupdn = a.bindrule['expression']
- groupdn = DN(groupdn.replace('ldap:///',''))
- try:
- cn = groupdn[0]['cn']
- except (IndexError, KeyError):
- cn = None
- if cn is None or cn != kw['group']:
- try:
- results.remove(a)
- except ValueError:
- pass
-
- if kw.get('targetgroup'):
- for a in acis:
- found = False
- if 'target' in a.target:
- target = a.target['target']['expression']
- targetdn = DN(target.replace('ldap:///',''))
- group_container_dn = DN(api.env.container_group, api.env.basedn)
- if targetdn.endswith(group_container_dn):
- try:
- cn = targetdn[0]['cn']
- except (IndexError, KeyError):
- cn = None
- if cn == kw['targetgroup']:
- found = True
- if not found:
- try:
- results.remove(a)
- except ValueError:
- pass
-
- if kw.get('filter'):
- if not kw['filter'].startswith('('):
- kw['filter'] = unicode('('+kw['filter']+')')
- for a in acis:
- if 'targetfilter' not in a.target or\
- not a.target['targetfilter']['expression'] or\
- a.target['targetfilter']['expression'] != kw['filter']:
- results.remove(a)
-
- if kw.get('subtree'):
- for a in acis:
- if 'target' in a.target:
- target = a.target['target']['expression']
- else:
- results.remove(a)
- continue
- if kw['subtree'].lower() != target.lower():
- try:
- results.remove(a)
- except ValueError:
- pass
-
- acis = []
- for result in results:
- if kw.get('raw', False):
- aci = dict(aci=unicode(result))
- else:
- aci = _aci_to_kw(ldap, result,
- pkey_only=kw.get('pkey_only', False))
- acis.append(aci)
-
- return dict(
- result=acis,
- count=len(acis),
- truncated=False,
- )
-
-
-@register()
-class aci_show(crud.Retrieve):
- """
- Display a single ACI given an ACI name.
- """
- NO_CLI = True
-
- has_output_params = (
- Str('aci',
- label=_('ACI'),
- ),
- )
-
- takes_options = (
- _prefix_option,
- DNParam('location?',
- label=_('Location of the ACI'),
- )
- )
-
- def execute(self, aciname, **kw):
- """
- Execute the aci-show operation.
-
- Returns the entry
-
- :param uid: The login name of the user to retrieve.
- :param kw: unused
- """
- ldap = self.api.Backend.ldap2
-
- dn = kw.get('location', self.api.env.basedn)
- entry = ldap.get_entry(dn, ['aci'])
-
- acis = _convert_strings_to_acis(entry.get('aci', []))
-
- aci = _find_aci_by_name(acis, kw['aciprefix'], aciname)
- if kw.get('raw', False):
- result = dict(aci=unicode(aci))
- else:
- result = _aci_to_kw(ldap, aci)
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-@register()
-class aci_rename(crud.Update):
- """
- Rename an ACI.
- """
- NO_CLI = True
- has_output_params = (
- Str('aci',
- label=_('ACI'),
- ),
- )
-
- takes_options = (
- _prefix_option,
- Str('newname',
- doc=_('New ACI name'),
- ),
- )
-
- msg_summary = _('Renamed ACI to "%(value)s"')
-
- def execute(self, aciname, **kw):
- ldap = self.api.Backend.ldap2
-
- entry = ldap.get_entry(self.api.env.basedn, ['aci'])
-
- acis = _convert_strings_to_acis(entry.get('aci', []))
- aci = _find_aci_by_name(acis, kw['aciprefix'], aciname)
-
- for a in acis:
- prefix, name = _parse_aci_name(a.name)
- if _make_aci_name(prefix, kw['newname']) == a.name:
- raise errors.DuplicateEntry()
-
- # The strategy here is to convert the ACI we're updating back into
- # a series of keywords. Then we replace any keywords that have been
- # updated and convert that back into an ACI and write it out.
- newkw = _aci_to_kw(ldap, aci)
- if 'selfaci' in newkw and newkw['selfaci'] == True:
- # selfaci is set in aci_to_kw to True only if the target is self
- kw['selfaci'] = True
- if 'aciname' in newkw:
- del newkw['aciname']
-
- # _make_aci is what is run in aci_add and validates the input.
- # Do this before we delete the existing ACI.
- newaci = _make_aci(ldap, None, kw['newname'], newkw)
-
- self.api.Command['aci_del'](aciname, aciprefix=kw['aciprefix'])
-
- result = self.api.Command['aci_add'](kw['newname'], **newkw)['result']
-
- if kw.get('raw', False):
- result = dict(aci=unicode(newaci))
- else:
- result = _aci_to_kw(ldap, newaci)
- return dict(
- result=result,
- value=pkey_to_value(kw['newname'], kw),
- )
diff --git a/ipalib/plugins/automember.py b/ipalib/plugins/automember.py
deleted file mode 100644
index 89b9dfadc..000000000
--- a/ipalib/plugins/automember.py
+++ /dev/null
@@ -1,802 +0,0 @@
-# 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/>.
-import uuid
-import time
-
-import ldap as _ldap
-import six
-
-from ipalib import api, errors, Str, StrEnum, DNParam, Flag, _, ngettext
-from ipalib import output, Command
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- entry_to_dict,
- LDAPObject,
- LDAPCreate,
- LDAPUpdate,
- LDAPDelete,
- LDAPSearch,
- LDAPRetrieve)
-from ipalib.request import context
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Auto Membership Rule.
-""") + _("""
-Bring clarity to the membership of hosts and users by configuring inclusive
-or exclusive regex patterns, you can automatically assign a new entries into
-a group or hostgroup based upon attribute information.
-""") + _("""
-A rule is directly associated with a group by name, so you cannot create
-a rule without an accompanying group or hostgroup.
-""") + _("""
-A condition is a regular expression used by 389-ds to match a new incoming
-entry with an automember rule. If it matches an inclusive rule then the
-entry is added to the appropriate group or hostgroup.
-""") + _("""
-A default group or hostgroup could be specified for entries that do not
-match any rule. In case of user entries this group will be a fallback group
-because all users are by default members of group specified in IPA config.
-""") + _("""
-The automember-rebuild command can be used to retroactively run automember rules
-against existing entries, thus rebuilding their membership.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Add the initial group or hostgroup:
- ipa hostgroup-add --desc="Web Servers" webservers
- ipa group-add --desc="Developers" devel
-""") + _("""
- Add the initial rule:
- ipa automember-add --type=hostgroup webservers
- ipa automember-add --type=group devel
-""") + _("""
- Add a condition to the rule:
- ipa automember-add-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
- ipa automember-add-condition --key=manager --type=group --inclusive-regex=^uid=mscott devel
-""") + _("""
- Add an exclusive condition to the rule to prevent auto assignment:
- ipa automember-add-condition --key=fqdn --type=hostgroup --exclusive-regex=^web5\.example\.com webservers
-""") + _("""
- Add a host:
- ipa host-add web1.example.com
-""") + _("""
- Add a user:
- ipa user-add --first=Tim --last=User --password tuser1 --manager=mscott
-""") + _("""
- Verify automembership:
- ipa hostgroup-show webservers
- Host-group: webservers
- Description: Web Servers
- Member hosts: web1.example.com
-
- ipa group-show devel
- Group name: devel
- Description: Developers
- GID: 1004200000
- Member users: tuser
-""") + _("""
- Remove a condition from the rule:
- ipa automember-remove-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
-""") + _("""
- Modify the automember rule:
- ipa automember-mod
-""") + _("""
- Set the default (fallback) target group:
- ipa automember-default-group-set --default-group=webservers --type=hostgroup
- ipa automember-default-group-set --default-group=ipausers --type=group
-""") + _("""
- Remove the default (fallback) target group:
- ipa automember-default-group-remove --type=hostgroup
- ipa automember-default-group-remove --type=group
-""") + _("""
- Show the default (fallback) target group:
- ipa automember-default-group-show --type=hostgroup
- ipa automember-default-group-show --type=group
-""") + _("""
- Find all of the automember rules:
- ipa automember-find
-""") + _("""
- Display a automember rule:
- ipa automember-show --type=hostgroup webservers
- ipa automember-show --type=group devel
-""") + _("""
- Delete an automember rule:
- ipa automember-del --type=hostgroup webservers
- ipa automember-del --type=group devel
-""") + _("""
- Rebuild membership for all users:
- ipa automember-rebuild --type=group
-""") + _("""
- Rebuild membership for all hosts:
- ipa automember-rebuild --type=hostgroup
-""") + _("""
- Rebuild membership for specified users:
- ipa automember-rebuild --users=tuser1 --users=tuser2
-""") + _("""
- Rebuild membership for specified hosts:
- ipa automember-rebuild --hosts=web1.example.com --hosts=web2.example.com
-""")
-
-register = Registry()
-
-# Options used by Condition Add and Remove.
-INCLUDE_RE = 'automemberinclusiveregex'
-EXCLUDE_RE = 'automemberexclusiveregex'
-
-REBUILD_TASK_CONTAINER = DN(('cn', 'automember rebuild membership'),
- ('cn', 'tasks'),
- ('cn', 'config'))
-
-
-regex_attrs = (
- Str('automemberinclusiveregex*',
- cli_name='inclusive_regex',
- label=_('Inclusive Regex'),
- doc=_('Inclusive Regex'),
- alwaysask=True,
- ),
- Str('automemberexclusiveregex*',
- cli_name='exclusive_regex',
- label=_('Exclusive Regex'),
- doc=_('Exclusive Regex'),
- alwaysask=True,
- ),
- Str('key',
- label=_('Attribute Key'),
- doc=_('Attribute to filter via regex. For example fqdn for a host, or manager for a user'),
- flags=['no_create', 'no_update', 'no_search']
- ),
-)
-
-group_type = (
- StrEnum('type',
- label=_('Grouping Type'),
- doc=_('Grouping to which the rule applies'),
- values=(u'group', u'hostgroup', ),
- ),
-)
-
-automember_rule = (
- Str('cn',
- cli_name='automember_rule',
- label=_('Automember Rule'),
- doc=_('Automember Rule'),
- normalizer=lambda value: value.lower(),
- ),
-)
-
-
-@register()
-class automember(LDAPObject):
-
- """
- Bring automember to a hostgroup with an Auto Membership Rule.
- """
-
- container_dn = api.env.container_automember
-
- object_name = 'Automember rule'
- object_name_plural = 'Automember rules'
- object_class = ['top', 'automemberregexrule']
- permission_filter_objectclasses = ['automemberregexrule']
- default_attributes = [
- 'automemberinclusiveregex', 'automemberexclusiveregex',
- 'cn', 'automembertargetgroup', 'description', 'automemberdefaultgroup'
- ]
- managed_permissions = {
- 'System: Read Automember Definitions': {
- 'non_object': True,
- 'ipapermlocation': DN(container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=automemberdefinition)'},
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'automemberscope', 'automemberfilter',
- 'automembergroupingattr', 'automemberdefaultgroup',
- 'automemberdisabled',
- },
- 'default_privileges': {'Automember Readers',
- 'Automember Task Administrator'},
- },
- 'System: Read Automember Rules': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass', 'automembertargetgroup', 'description',
- 'automemberexclusiveregex', 'automemberinclusiveregex',
- },
- 'default_privileges': {'Automember Readers',
- 'Automember Task Administrator'},
- },
- 'System: Read Automember Tasks': {
- 'non_object': True,
- 'ipapermlocation': DN('cn=tasks', 'cn=config'),
- 'ipapermtarget': DN('cn=*', REBUILD_TASK_CONTAINER),
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Automember Task Administrator'},
- },
- }
-
- label = _('Auto Membership Rule')
-
- takes_params = (
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this auto member rule'),
- ),
- Str('automemberdefaultgroup?',
- cli_name='default_group',
- label=_('Default (fallback) Group'),
- doc=_('Default group for entries to land'),
- flags=['no_create', 'no_update', 'no_search']
- ),
- )
-
- def dn_exists(self, otype, oname):
- ldap = self.api.Backend.ldap2
- dn = self.api.Object[otype].get_dn(oname)
- try:
- entry = ldap.get_entry(dn, [])
- except errors.NotFound:
- raise errors.NotFound(
- reason=_(u'%(otype)s "%(oname)s" not found') %
- dict(otype=otype, oname=oname)
- )
- return entry.dn
-
- def get_dn(self, *keys, **options):
- if self.parent_object:
- parent_dn = self.api.Object[self.parent_object].get_dn(*keys[:-1])
- else:
- parent_dn = DN(self.container_dn, api.env.basedn)
- grouptype = options['type']
- try:
- ndn = DN(('cn', keys[-1]), ('cn', grouptype), parent_dn)
- except IndexError:
- ndn = DN(('cn', grouptype), parent_dn)
- return ndn
-
- def check_attr(self, attr):
- """
- Verify that the user supplied key is a valid attribute in the schema
- """
- ldap = self.api.Backend.ldap2
- obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr)
- if obj is not None:
- return obj
- else:
- raise errors.NotFound(reason=_('%s is not a valid attribute.') % attr)
-
-
-def automember_container_exists(ldap):
- try:
- ldap.get_entry(DN(api.env.container_automember, api.env.basedn), [])
- except errors.NotFound:
- return False
- return True
-
-
-@register()
-class automember_add(LDAPCreate):
- __doc__ = _("""
- Add an automember rule.
- """)
- takes_options = LDAPCreate.takes_options + group_type
- takes_args = automember_rule
- msg_summary = _('Added automember rule "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- entry_attrs['cn'] = keys[-1]
- if not automember_container_exists(self.api.Backend.ldap2):
- raise errors.NotFound(reason=_('Auto Membership is not configured'))
- entry_attrs['automembertargetgroup'] = self.obj.dn_exists(options['type'], keys[-1])
- return dn
-
- def execute(self, *keys, **options):
- result = super(automember_add, self).execute(*keys, **options)
- result['value'] = pkey_to_value(keys[-1], options)
- return result
-
-
-@register()
-class automember_add_condition(LDAPUpdate):
- __doc__ = _("""
- Add conditions to an automember rule.
- """)
- has_output_params = (
- Str('failed',
- label=_('Failed to add'),
- flags=['suppress_empty'],
- ),
- )
-
- takes_options = regex_attrs + group_type
- takes_args = automember_rule
- msg_summary = _('Added condition(s) to "%(value)s"')
-
- # Prepare the output to expect failed results
- has_output = (
- output.summary,
- output.Entry('result'),
- output.value,
- output.Output('failed',
- type=dict,
- doc=_('Conditions that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of conditions added'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # Check to see if the automember rule exists
- try:
- dn = ldap.get_entry(dn, []).dn
- except errors.NotFound:
- raise errors.NotFound(reason=_(u'Auto member rule: %s not found!') % keys[0])
- # Define container key
- key = options['key']
- # Check to see if the attribute is valid
- self.obj.check_attr(key)
-
- key = '%s=' % key
- completed = 0
- failed = {'failed': {}}
-
- for attr in (INCLUDE_RE, EXCLUDE_RE):
- failed['failed'][attr] = []
- if attr in options and options[attr]:
- entry_attrs[attr] = [key + condition for condition in options[attr]]
- completed += len(entry_attrs[attr])
- try:
- old_entry = ldap.get_entry(dn, [attr])
- for regex in old_entry.keys():
- if not isinstance(entry_attrs[regex], (list, tuple)):
- entry_attrs[regex] = [entry_attrs[regex]]
- duplicate = set(old_entry[regex]) & set(entry_attrs[regex])
- if len(duplicate) > 0:
- completed -= 1
- else:
- entry_attrs[regex] = list(entry_attrs[regex]) + old_entry[regex]
- except errors.NotFound:
- failed['failed'][attr].append(regex)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- # Set failed and completed to they can be harvested in the execute super
- setattr(context, 'failed', failed)
- setattr(context, 'completed', completed)
- setattr(context, 'entry_attrs', entry_attrs)
-
- # Make sure to returned the failed results if there is nothing to remove
- if completed == 0:
- ldap.get_entry(dn, attrs_list)
- raise errors.EmptyModlist
- return dn
-
- def execute(self, *keys, **options):
- __doc__ = _("""
- Override this so we can add completed and failed to the return result.
- """)
- try:
- result = super(automember_add_condition, self).execute(*keys, **options)
- except errors.EmptyModlist:
- result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
- result['failed'] = getattr(context, 'failed')
- result['completed'] = getattr(context, 'completed')
- result['value'] = pkey_to_value(keys[-1], options)
- return result
-
-
-@register()
-class automember_remove_condition(LDAPUpdate):
- __doc__ = _("""
- Remove conditions from an automember rule.
- """)
- takes_options = regex_attrs + group_type
- takes_args = automember_rule
- msg_summary = _('Removed condition(s) from "%(value)s"')
-
- # Prepare the output to expect failed results
- has_output = (
- output.summary,
- output.Entry('result'),
- output.value,
- output.Output('failed',
- type=dict,
- doc=_('Conditions that could not be removed'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of conditions removed'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # Check to see if the automember rule exists
- try:
- ldap.get_entry(dn, [])
- except errors.NotFound:
- raise errors.NotFound(reason=_(u'Auto member rule: %s not found!') % keys[0])
-
- # Define container key
- type_attr_default = {'group': 'manager', 'hostgroup': 'fqdn'}
- if 'key' in options:
- key = options['key']
- else:
- key = type_attr_default[options['type']]
-
- key = '%s=' % key
- completed = 0
- failed = {'failed': {}}
-
- # Check to see if there are existing exclusive conditions present.
- dn = ldap.get_entry(dn, [EXCLUDE_RE]).dn
-
- for attr in (INCLUDE_RE, EXCLUDE_RE):
- failed['failed'][attr] = []
- if attr in options and options[attr]:
- entry_attrs[attr] = [key + condition for condition in options[attr]]
- entry_attrs_ = ldap.get_entry(dn, [attr])
- old_entry = entry_attrs_.get(attr, [])
- for regex in entry_attrs[attr]:
- if regex in old_entry:
- old_entry.remove(regex)
- completed += 1
- else:
- failed['failed'][attr].append(regex)
- entry_attrs[attr] = old_entry
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- # Set failed and completed to they can be harvested in the execute super
- setattr(context, 'failed', failed)
- setattr(context, 'completed', completed)
- setattr(context, 'entry_attrs', entry_attrs)
-
- # Make sure to returned the failed results if there is nothing to remove
- if completed == 0:
- ldap.get_entry(dn, attrs_list)
- raise errors.EmptyModlist
- return dn
-
- def execute(self, *keys, **options):
- __doc__ = _("""
- Override this so we can set completed and failed.
- """)
- try:
- result = super(automember_remove_condition, self).execute(*keys, **options)
- except errors.EmptyModlist:
- result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
- result['failed'] = getattr(context, 'failed')
- result['completed'] = getattr(context, 'completed')
- result['value'] = pkey_to_value(keys[-1], options)
- return result
-
-
-@register()
-class automember_mod(LDAPUpdate):
- __doc__ = _("""
- Modify an automember rule.
- """)
- takes_args = automember_rule
- takes_options = LDAPUpdate.takes_options + group_type
- msg_summary = _('Modified automember rule "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(automember_mod, self).execute(*keys, **options)
- result['value'] = pkey_to_value(keys[-1], options)
- return result
-
-
-@register()
-class automember_del(LDAPDelete):
- __doc__ = _("""
- Delete an automember rule.
- """)
- takes_args = automember_rule
- takes_options = group_type
- msg_summary = _('Deleted automember rule "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(automember_del, self).execute(*keys, **options)
- result['value'] = pkey_to_value([keys[-1]], options)
- return result
-
-
-@register()
-class automember_find(LDAPSearch):
- __doc__ = _("""
- Search for automember rules.
- """)
- takes_options = group_type
- has_output_params = LDAPSearch.has_output_params + automember_rule + regex_attrs
-
- msg_summary = ngettext(
- '%(count)d rules matched', '%(count)d rules matched', 0
- )
-
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- scope = ldap.SCOPE_SUBTREE
- ndn = DN(('cn', options['type']), base_dn)
- return (filters, ndn, scope)
-
-
-@register()
-class automember_show(LDAPRetrieve):
- __doc__ = _("""
- Display information about an automember rule.
- """)
- takes_args = automember_rule
- takes_options = group_type
- has_output_params = LDAPRetrieve.has_output_params + regex_attrs
-
- def execute(self, *keys, **options):
- result = super(automember_show, self).execute(*keys, **options)
- result['value'] = pkey_to_value(keys[-1], options)
- return result
-
-
-@register()
-class automember_default_group_set(LDAPUpdate):
- __doc__ = _("""
- Set default (fallback) group for all unmatched entries.
- """)
-
- takes_options = (
- Str('automemberdefaultgroup',
- cli_name='default_group',
- label=_('Default (fallback) Group'),
- doc=_('Default (fallback) group for entries to land'),
- flags=['no_create', 'no_update']
- ),
- ) + group_type
- msg_summary = _('Set default (fallback) group for automember "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = DN(('cn', options['type']), api.env.container_automember,
- api.env.basedn)
- entry_attrs['automemberdefaultgroup'] = self.obj.dn_exists(options['type'], options['automemberdefaultgroup'])
- return dn
-
- def execute(self, *keys, **options):
- result = super(automember_default_group_set, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['type'], options)
- return result
-
-
-@register()
-class automember_default_group_remove(LDAPUpdate):
- __doc__ = _("""
- Remove default (fallback) group for all unmatched entries.
- """)
-
- takes_options = group_type
- msg_summary = _('Removed default (fallback) group for automember "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = DN(('cn', options['type']), api.env.container_automember,
- api.env.basedn)
- attr = 'automemberdefaultgroup'
-
- entry_attrs_ = ldap.get_entry(dn, [attr])
-
- if attr not in entry_attrs_:
- raise errors.NotFound(reason=_(u'No default (fallback) group set'))
- else:
- entry_attrs[attr] = []
- return entry_attrs_.dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if 'automemberdefaultgroup' not in entry_attrs:
- entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set'))
- return dn
-
- def execute(self, *keys, **options):
- result = super(automember_default_group_remove, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['type'], options)
- return result
-
-
-@register()
-class automember_default_group_show(LDAPRetrieve):
- __doc__ = _("""
- Display information about the default (fallback) automember groups.
- """)
- takes_options = group_type
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- dn = DN(('cn', options['type']), api.env.container_automember,
- api.env.basedn)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if 'automemberdefaultgroup' not in entry_attrs:
- entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set'))
- return dn
-
- def execute(self, *keys, **options):
- result = super(automember_default_group_show, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['type'], options)
- return result
-
-
-@register()
-class automember_rebuild(Command):
- __doc__ = _('Rebuild auto membership.')
- # TODO: Add a --dry-run option:
- # https://fedorahosted.org/freeipa/ticket/3936
- takes_options = (
- group_type[0].clone(
- required=False,
- label=_('Rebuild membership for all members of a grouping')
- ),
- Str(
- 'users*',
- label=_('Users'),
- doc=_('Rebuild membership for specified users'),
- ),
- Str(
- 'hosts*',
- label=_('Hosts'),
- doc=_('Rebuild membership for specified hosts'),
- ),
- Flag(
- 'no_wait?',
- default=False,
- label=_('No wait'),
- doc=_("Don't wait for rebuilding membership"),
- ),
- )
- has_output = output.standard_entry
- has_output_params = (
- DNParam(
- 'dn',
- label=_('Task DN'),
- doc=_('DN of the started task'),
- ),
- )
-
- def validate(self, **kw):
- """
- Validation rules:
- - at least one of 'type', 'users', 'hosts' is required
- - 'users' and 'hosts' cannot be combined together
- - if 'users' and 'type' are specified, 'type' must be 'group'
- - if 'hosts' and 'type' are specified, 'type' must be 'hostgroup'
- """
- super(automember_rebuild, self).validate(**kw)
- users, hosts, gtype = kw.get('users'), kw.get('hosts'), kw.get('type')
-
- if not (gtype or users or hosts):
- raise errors.MutuallyExclusiveError(
- reason=_('at least one of options: type, users, hosts must be '
- 'specified')
- )
-
- if users and hosts:
- raise errors.MutuallyExclusiveError(
- reason=_("users and hosts cannot both be set")
- )
- if gtype == 'group' and hosts:
- raise errors.MutuallyExclusiveError(
- reason=_("hosts cannot be set when type is 'group'")
- )
- if gtype == 'hostgroup' and users:
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be set when type is 'hostgroup'")
- )
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- cn = str(uuid.uuid4())
-
- gtype = options.get('type')
- if not gtype:
- gtype = 'group' if options.get('users') else 'hostgroup'
-
- types = {
- 'group': (
- 'user',
- 'users',
- DN(api.env.container_user, api.env.basedn)
- ),
- 'hostgroup': (
- 'host',
- 'hosts',
- DN(api.env.container_host, api.env.basedn)
- ),
- }
-
- obj_name, opt_name, basedn = types[gtype]
- obj = self.api.Object[obj_name]
-
- names = options.get(opt_name)
- if names:
- for name in names:
- try:
- obj.get_dn_if_exists(name)
- except errors.NotFound:
- obj.handle_not_found(name)
- search_filter = ldap.make_filter_from_attr(
- obj.primary_key.name,
- names,
- rules=ldap.MATCH_ANY
- )
- else:
- search_filter = '(%s=*)' % obj.primary_key.name
-
- task_dn = DN(('cn', cn), REBUILD_TASK_CONTAINER)
-
- entry = ldap.make_entry(
- task_dn,
- objectclass=['top', 'extensibleObject'],
- cn=[cn],
- basedn=[basedn],
- filter=[search_filter],
- scope=['sub'],
- ttl=[3600])
- ldap.add_entry(entry)
-
- summary = _('Automember rebuild membership task started')
- result = {'dn': task_dn}
-
- if not options.get('no_wait'):
- summary = _('Automember rebuild membership task completed')
- result = {}
- start_time = time.time()
-
- while True:
- try:
- task = ldap.get_entry(task_dn)
- except errors.NotFound:
- break
-
- if 'nstaskexitcode' in task:
- if str(task.single_value['nstaskexitcode']) == '0':
- summary=task.single_value['nstaskstatus']
- break
- else:
- raise errors.DatabaseError(
- desc=task.single_value['nstaskstatus'],
- info=_("Task DN = '%s'" % task_dn))
- time.sleep(1)
- if time.time() > (start_time + 60):
- raise errors.TaskTimeout(task=_('Automember'), task_dn=task_dn)
-
- return dict(
- result=result,
- summary=unicode(summary),
- value=pkey_to_value(None, options))
diff --git a/ipalib/plugins/automount.py b/ipalib/plugins/automount.py
deleted file mode 100644
index c4cf2d6db..000000000
--- a/ipalib/plugins/automount.py
+++ /dev/null
@@ -1,841 +0,0 @@
-# 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 six
-
-from ipalib import api, errors
-from ipalib import Str, IA5Str
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPQuery,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve)
-from ipalib import _, ngettext
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Automount
-
-Stores automount(8) configuration for autofs(8) in IPA.
-
-The base of an automount configuration is the configuration file auto.master.
-This is also the base location in IPA. Multiple auto.master configurations
-can be stored in separate locations. A location is implementation-specific
-with the default being a location named 'default'. For example, you can have
-locations by geographic region, by floor, by type, etc.
-
-Automount has three basic object types: locations, maps and keys.
-
-A location defines a set of maps anchored in auto.master. This allows you
-to store multiple automount configurations. A location in itself isn't
-very interesting, it is just a point to start a new automount map.
-
-A map is roughly equivalent to a discrete automount file and provides
-storage for keys.
-
-A key is a mount point associated with a map.
-
-When a new location is created, two maps are automatically created for
-it: auto.master and auto.direct. auto.master is the root map for all
-automount maps for the location. auto.direct is the default map for
-direct mounts and is mounted on /-.
-
-An automount map may contain a submount key. This key defines a mount
-location within the map that references another map. This can be done
-either using automountmap-add-indirect --parentmap or manually
-with automountkey-add and setting info to "-type=autofs :<mapname>".
-
-EXAMPLES:
-
-Locations:
-
- Create a named location, "Baltimore":
- ipa automountlocation-add baltimore
-
- Display the new location:
- ipa automountlocation-show baltimore
-
- Find available locations:
- ipa automountlocation-find
-
- Remove a named automount location:
- ipa automountlocation-del baltimore
-
- Show what the automount maps would look like if they were in the filesystem:
- ipa automountlocation-tofiles baltimore
-
- Import an existing configuration into a location:
- ipa automountlocation-import baltimore /etc/auto.master
-
- The import will fail if any duplicate entries are found. For
- continuous operation where errors are ignored, use the --continue
- option.
-
-Maps:
-
- Create a new map, "auto.share":
- ipa automountmap-add baltimore auto.share
-
- Display the new map:
- ipa automountmap-show baltimore auto.share
-
- Find maps in the location baltimore:
- ipa automountmap-find baltimore
-
- Create an indirect map with auto.share as a submount:
- ipa automountmap-add-indirect baltimore --parentmap=auto.share --mount=sub auto.man
-
- This is equivalent to:
-
- ipa automountmap-add-indirect baltimore --mount=/man auto.man
- ipa automountkey-add baltimore auto.man --key=sub --info="-fstype=autofs ldap:auto.share"
-
- Remove the auto.share map:
- ipa automountmap-del baltimore auto.share
-
-Keys:
-
- Create a new key for the auto.share map in location baltimore. This ties
- the map we previously created to auto.master:
- ipa automountkey-add baltimore auto.master --key=/share --info=auto.share
-
- Create a new key for our auto.share map, an NFS mount for man pages:
- ipa automountkey-add baltimore auto.share --key=man --info="-ro,soft,rsize=8192,wsize=8192 ipa.example.com:/shared/man"
-
- Find all keys for the auto.share map:
- ipa automountkey-find baltimore auto.share
-
- Find all direct automount keys:
- ipa automountkey-find baltimore --key=/-
-
- Remove the man key from the auto.share map:
- ipa automountkey-del baltimore auto.share --key=man
-""")
-
-"""
-Developer notes:
-
-RFC 2707bis http://www.padl.com/~lukeh/rfc2307bis.txt
-
-A few notes on automount:
-- The default parent when adding an indirect map is auto.master
-- This uses the short format for automount maps instead of the
- URL format. Support for ldap as a map source in nsswitch.conf was added
- in autofs version 4.1.3-197. Any version prior to that is not expected
- to work.
-- An indirect key should not begin with /
-
-As an example, the following automount files:
-
-auto.master:
-/- auto.direct
-/mnt auto.mnt
-
-auto.mnt:
-stuff -ro,soft,rsize=8192,wsize=8192 nfs.example.com:/vol/archive/stuff
-
-are equivalent to the following LDAP entries:
-
-# auto.master, automount, example.com
-dn: automountmapname=auto.master,cn=automount,dc=example,dc=com
-objectClass: automountMap
-objectClass: top
-automountMapName: auto.master
-
-# auto.direct, automount, example.com
-dn: automountmapname=auto.direct,cn=automount,dc=example,dc=com
-objectClass: automountMap
-objectClass: top
-automountMapName: auto.direct
-
-# /-, auto.master, automount, example.com
-dn: automountkey=/-,automountmapname=auto.master,cn=automount,dc=example,dc=co
- m
-objectClass: automount
-objectClass: top
-automountKey: /-
-automountInformation: auto.direct
-
-# auto.mnt, automount, example.com
-dn: automountmapname=auto.mnt,cn=automount,dc=example,dc=com
-objectClass: automountMap
-objectClass: top
-automountMapName: auto.mnt
-
-# /mnt, auto.master, automount, example.com
-dn: automountkey=/mnt,automountmapname=auto.master,cn=automount,dc=example,dc=
- com
-objectClass: automount
-objectClass: top
-automountKey: /mnt
-automountInformation: auto.mnt
-
-# stuff, auto.mnt, automount, example.com
-dn: automountkey=stuff,automountmapname=auto.mnt,cn=automount,dc=example,dc=com
-objectClass: automount
-objectClass: top
-automountKey: stuff
-automountInformation: -ro,soft,rsize=8192,wsize=8192 nfs.example.com:/vol/arch
- ive/stuff
-
-"""
-
-register = Registry()
-
-DIRECT_MAP_KEY = u'/-'
-
-@register()
-class automountlocation(LDAPObject):
- """
- Location container for automount maps.
- """
- container_dn = api.env.container_automount
- object_name = _('automount location')
- object_name_plural = _('automount locations')
- object_class = ['nscontainer']
- default_attributes = ['cn']
- label = _('Automount Locations')
- label_singular = _('Automount Location')
- permission_filter_objectclasses = ['nscontainer']
- managed_permissions = {
- 'System: Read Automount Configuration': {
- # Single permission for all automount-related entries
- 'non_object': True,
- 'ipapermlocation': DN(container_dn, api.env.basedn),
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass',
- 'automountinformation', 'automountkey', 'description',
- 'automountmapname', 'description',
- },
- },
- 'System: Add Automount Locations': {
- 'ipapermright': {'add'},
- 'default_privileges': {'Automount Administrators'},
- },
- 'System: Remove Automount Locations': {
- 'ipapermright': {'delete'},
- 'default_privileges': {'Automount Administrators'},
- },
- }
-
- takes_params = (
- Str('cn',
- cli_name='location',
- label=_('Location'),
- doc=_('Automount location name.'),
- primary_key=True,
- ),
- )
-
-
-@register()
-class automountlocation_add(LDAPCreate):
- __doc__ = _('Create a new automount location.')
-
- msg_summary = _('Added automount location "%(value)s"')
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- # create auto.master for the new location
- self.api.Command['automountmap_add'](keys[-1], u'auto.master')
-
- # add additional pre-created maps and keys
- # IMPORTANT: add pre-created maps/keys to DEFAULT_MAPS/DEFAULT_KEYS
- # so that they do not cause conflicts during import operation
- self.api.Command['automountmap_add_indirect'](
- keys[-1], u'auto.direct', key=DIRECT_MAP_KEY
- )
- return dn
-
-
-@register()
-class automountlocation_del(LDAPDelete):
- __doc__ = _('Delete an automount location.')
-
- msg_summary = _('Deleted automount location "%(value)s"')
-
-
-@register()
-class automountlocation_show(LDAPRetrieve):
- __doc__ = _('Display an automount location.')
-
-
-@register()
-class automountlocation_find(LDAPSearch):
- __doc__ = _('Search for an automount location.')
-
- msg_summary = ngettext(
- '%(count)d automount location matched',
- '%(count)d automount locations matched', 0
- )
-
-
-@register()
-class automountlocation_tofiles(LDAPQuery):
- __doc__ = _('Generate automount files for a specific location.')
-
- def execute(self, *args, **options):
- self.api.Command['automountlocation_show'](args[0])
-
- result = self.api.Command['automountkey_find'](args[0], u'auto.master')
- maps = result['result']
-
- # maps, truncated
- # TODO: handle truncated results
- # ?use ldap.find_entries instead of automountkey_find?
-
- keys = {}
- mapnames = [u'auto.master']
- for m in maps:
- info = m['automountinformation'][0]
- mapnames.append(info)
- key = info.split(None)
- result = self.api.Command['automountkey_find'](args[0], key[0])
- keys[info] = result['result']
- # TODO: handle truncated results, same as above
-
- allmaps = self.api.Command['automountmap_find'](args[0])['result']
- orphanmaps = []
- for m in allmaps:
- if m['automountmapname'][0] not in mapnames:
- orphanmaps.append(m)
-
- orphankeys = []
- # Collect all the keys for the orphaned maps
- for m in orphanmaps:
- key = m['automountmapname']
- result = self.api.Command['automountkey_find'](args[0], key[0])
- orphankeys.append(result['result'])
-
- return dict(result=dict(maps=maps, keys=keys,
- orphanmaps=orphanmaps, orphankeys=orphankeys))
-
-
-@register()
-class automountmap(LDAPObject):
- """
- Automount map object.
- """
- parent_object = 'automountlocation'
- container_dn = api.env.container_automount
- object_name = _('automount map')
- object_name_plural = _('automount maps')
- object_class = ['automountmap']
- permission_filter_objectclasses = ['automountmap']
- default_attributes = ['automountmapname', 'description']
-
- takes_params = (
- IA5Str('automountmapname',
- cli_name='map',
- label=_('Map'),
- doc=_('Automount map name.'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- )
-
- managed_permissions = {
- 'System: Add Automount Maps': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Add Automount maps";allow (add) groupdn = "ldap:///cn=Add Automount maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- 'System: Modify Automount Maps': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'automountmapname', 'description'},
- 'replaces': [
- '(targetattr = "automountmapname || description")(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Modify Automount maps";allow (write) groupdn = "ldap:///cn=Modify Automount maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- 'System: Remove Automount Maps': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Remove Automount maps";allow (delete) groupdn = "ldap:///cn=Remove Automount maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- }
-
- label = _('Automount Maps')
- label_singular = _('Automount Map')
-
-
-@register()
-class automountmap_add(LDAPCreate):
- __doc__ = _('Create a new automount map.')
-
- msg_summary = _('Added automount map "%(value)s"')
-
-
-@register()
-class automountmap_del(LDAPDelete):
- __doc__ = _('Delete an automount map.')
-
- msg_summary = _('Deleted automount map "%(value)s"')
-
- def post_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- # delete optional parental connection (direct maps may not have this)
- try:
- entry_attrs = ldap.find_entry_by_attr(
- 'automountinformation', keys[0], 'automount',
- base_dn=DN(self.obj.container_dn, api.env.basedn)
- )
- ldap.delete_entry(entry_attrs)
- except errors.NotFound:
- pass
- return True
-
-
-@register()
-class automountmap_mod(LDAPUpdate):
- __doc__ = _('Modify an automount map.')
-
- msg_summary = _('Modified automount map "%(value)s"')
-
-
-@register()
-class automountmap_find(LDAPSearch):
- __doc__ = _('Search for an automount map.')
-
- msg_summary = ngettext(
- '%(count)d automount map matched',
- '%(count)d automount maps matched', 0
- )
-
-
-@register()
-class automountmap_show(LDAPRetrieve):
- __doc__ = _('Display an automount map.')
-
-
-@register()
-class automountkey(LDAPObject):
- __doc__ = _('Automount key object.')
-
- parent_object = 'automountmap'
- container_dn = api.env.container_automount
- object_name = _('automount key')
- object_name_plural = _('automount keys')
- object_class = ['automount']
- permission_filter_objectclasses = ['automount']
- default_attributes = [
- 'automountkey', 'automountinformation', 'description'
- ]
- rdn_is_primary_key = True
- rdn_separator = ' '
-
- takes_params = (
- IA5Str('automountkey',
- cli_name='key',
- label=_('Key'),
- doc=_('Automount key name.'),
- flags=('req_update',),
- ),
- IA5Str('automountinformation',
- cli_name='info',
- label=_('Mount information'),
- ),
- Str('description',
- label=_('description'),
- primary_key=True,
- required=False,
- flags=['no_create', 'no_update', 'no_search', 'no_output'],
- exclude='webui',
- ),
- )
-
- managed_permissions = {
- 'System: Add Automount Keys': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///automountkey=*,automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Add Automount keys";allow (add) groupdn = "ldap:///cn=Add Automount keys,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(objectclass=automount)")(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Add Automount keys";allow (add) groupdn = "ldap:///cn=Add Automount keys,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- 'System: Modify Automount Keys': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'automountinformation', 'automountkey', 'description',
- },
- 'replaces': [
- '(targetattr = "automountkey || automountinformation || description")(targetfilter = "(objectclass=automount)")(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Modify Automount keys";allow (write) groupdn = "ldap:///cn=Modify Automount keys,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- 'System: Remove Automount Keys': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///automountkey=*,automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Remove Automount keys";allow (delete) groupdn = "ldap:///cn=Remove Automount keys,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(objectclass=automount)")(target = "ldap:///automountmapname=*,cn=automount,$SUFFIX")(version 3.0;acl "permission:Remove Automount keys";allow (delete) groupdn = "ldap:///cn=Remove Automount keys,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Automount Administrators'},
- },
- }
-
- num_parents = 2
- label = _('Automount Keys')
- label_singular = _('Automount Key')
- already_exists_msg = _('The key,info pair must be unique. A key named %(key)s with info %(info)s already exists')
- key_already_exists_msg = _('key named %(key)s already exists')
- object_not_found_msg = _('The automount key %(key)s with info %(info)s does not exist')
-
- def get_dn(self, *keys, **kwargs):
- # all commands except for create send pk in keys, too
- # create cannot due to validation in frontend.py
- ldap = self.backend
- if len(keys) == self.num_parents:
- try:
- pkey = kwargs[self.primary_key.name]
- except KeyError:
- raise ValueError('Not enough keys and pkey not in kwargs')
- parent_keys = keys
- else:
- pkey = keys[-1]
- parent_keys = keys[:-1]
-
- parent_dn = self.api.Object[self.parent_object].get_dn(*parent_keys)
- dn = self.backend.make_dn_from_attr(
- self.primary_key.name,
- pkey,
- parent_dn
- )
- # If we're doing an add then just return the dn we created, there
- # is no need to check for it.
- if kwargs.get('add_operation', False):
- return dn
- # We had an older mechanism where description consisted of
- # 'automountkey automountinformation' so we could support multiple
- # direct maps. This made showing keys nearly impossible since it
- # required automountinfo to show, which if you had you didn't need
- # to look at the key. We still support existing entries but now
- # only create this type of dn when the key is /-
- #
- # First we look with the information given, then try to search for
- # the right entry.
- try:
- dn = ldap.get_entry(dn, ['*']).dn
- except errors.NotFound:
- if kwargs.get('automountinformation', False):
- sfilter = '(&(automountkey=%s)(automountinformation=%s))' % \
- (kwargs['automountkey'], kwargs['automountinformation'])
- else:
- sfilter = '(automountkey=%s)' % kwargs['automountkey']
- basedn = DN(('automountmapname', parent_keys[1]),
- ('cn', parent_keys[0]), self.container_dn,
- api.env.basedn)
- attrs_list = ['*']
- entries = ldap.get_entries(
- basedn, ldap.SCOPE_ONELEVEL, sfilter, attrs_list)
- if len(entries) > 1:
- raise errors.NotFound(reason=_('More than one entry with key %(key)s found, use --info to select specific entry.') % dict(key=pkey))
- dn = entries[0].dn
-
- return dn
-
- def handle_not_found(self, *keys):
- pkey = keys[-1]
- key = pkey.split(self.rdn_separator)[0]
- info = self.rdn_separator.join(pkey.split(self.rdn_separator)[1:])
- raise errors.NotFound(
- reason=self.object_not_found_msg % {
- 'key': key, 'info': info,
- }
- )
-
- def handle_duplicate_entry(self, *keys):
- pkey = keys[-1]
- key = pkey.split(self.rdn_separator)[0]
- info = self.rdn_separator.join(pkey.split(self.rdn_separator)[1:])
- if info:
- raise errors.DuplicateEntry(
- message=self.already_exists_msg % {
- 'key': key, 'info': info,
- }
- )
- else:
- raise errors.DuplicateEntry(
- message=self.key_already_exists_msg % {
- 'key': key,
- }
- )
-
- def get_pk(self, key, info=None):
- if key == DIRECT_MAP_KEY and info:
- return self.rdn_separator.join((key,info))
- else:
- return key
-
- def check_key_uniqueness(self, location, map, **keykw):
- info = None
- key = keykw.get('automountkey')
- if key is None:
- return
-
- entries = self.methods.find(location, map, automountkey=key)['result']
- if len(entries) > 0:
- if key == DIRECT_MAP_KEY:
- info = keykw.get('automountinformation')
- entries = self.methods.find(location, map, **keykw)['result']
- if len(entries) > 0:
- self.handle_duplicate_entry(location, map, self.get_pk(key, info))
- else: return
- self.handle_duplicate_entry(location, map, self.get_pk(key, info))
-
-
-@register()
-class automountkey_add(LDAPCreate):
- __doc__ = _('Create a new automount key.')
-
- msg_summary = _('Added automount key "%(value)s"')
-
- internal_options = ['description', 'add_operation']
-
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- options.pop('add_operation', None)
- options.pop('description', None)
- self.obj.check_key_uniqueness(keys[-2], keys[-1], **options)
- return dn
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
-
- def execute(self, *keys, **options):
- key = options['automountkey']
- info = options.get('automountinformation', None)
- options[self.obj.primary_key.name] = self.obj.get_pk(key, info)
- options['add_operation'] = True
- result = super(automountkey_add, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['automountkey'], options)
- return result
-
-
-@register()
-class automountmap_add_indirect(LDAPCreate):
- __doc__ = _('Create a new indirect mount point.')
-
- msg_summary = _('Added automount indirect map "%(value)s"')
-
- takes_options = LDAPCreate.takes_options + (
- Str('key',
- cli_name='mount',
- label=_('Mount point'),
- ),
- Str('parentmap?',
- cli_name='parentmap',
- label=_('Parent map'),
- doc=_('Name of parent automount map (default: auto.master).'),
- default=u'auto.master',
- autofill=True,
- ),
- )
-
- def execute(self, *keys, **options):
- parentmap = options.pop('parentmap', None)
- key = options.pop('key')
- result = self.api.Command['automountmap_add'](*keys, **options)
- try:
- if parentmap != u'auto.master':
- if key.startswith('/'):
- raise errors.ValidationError(name='mount',
- error=_('mount point is relative to parent map, '
- 'cannot begin with /'))
- location = keys[0]
- map = keys[1]
- options['automountinformation'] = map
-
- # Ensure the referenced map exists
- self.api.Command['automountmap_show'](location, parentmap)
- # Add a submount key
- self.api.Command['automountkey_add'](
- location, parentmap, automountkey=key,
- automountinformation='-fstype=autofs ldap:%s' % map)
- else: # adding to auto.master
- # Ensure auto.master exists
- self.api.Command['automountmap_show'](keys[0], parentmap)
- self.api.Command['automountkey_add'](
- keys[0], u'auto.master', automountkey=key,
- automountinformation=keys[1])
- except Exception:
- # The key exists, drop the map
- self.api.Command['automountmap_del'](*keys)
- raise
- return result
-
-
-@register()
-class automountkey_del(LDAPDelete):
- __doc__ = _('Delete an automount key.')
-
- msg_summary = _('Deleted automount key "%(value)s"')
-
- takes_options = LDAPDelete.takes_options + (
- IA5Str('automountkey',
- cli_name='key',
- label=_('Key'),
- doc=_('Automount key name.'),
- ),
- IA5Str('automountinformation?',
- cli_name='info',
- label=_('Mount information'),
- ),
- )
- def get_options(self):
- for option in super(automountkey_del, self).get_options():
- if option.name == 'continue':
- # TODO: hide for now - remove in future major release
- yield option.clone(exclude='webui',
- flags=['no_option', 'no_output'])
- else:
- yield option
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
-
- def execute(self, *keys, **options):
- keys += (self.obj.get_pk(options['automountkey'],
- options.get('automountinformation', None)),)
- options[self.obj.primary_key.name] = self.obj.get_pk(
- options['automountkey'],
- options.get('automountinformation', None))
- result = super(automountkey_del, self).execute(*keys, **options)
- result['value'] = pkey_to_value([options['automountkey']], options)
- return result
-
-
-@register()
-class automountkey_mod(LDAPUpdate):
- __doc__ = _('Modify an automount key.')
-
- msg_summary = _('Modified automount key "%(value)s"')
-
- internal_options = ['newautomountkey']
-
- takes_options = LDAPUpdate.takes_options + (
- IA5Str('newautomountinformation?',
- cli_name='newinfo',
- label=_('New mount information'),
- ),
- )
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
-
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if 'newautomountkey' in options:
- entry_attrs['automountkey'] = options['newautomountkey']
- if 'newautomountinformation' in options:
- entry_attrs['automountinformation'] = options['newautomountinformation']
- return dn
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- key = options['automountkey']
- info = options.get('automountinformation', None)
- keys += (self.obj.get_pk(key, info), )
-
- # handle RDN changes
- if 'rename' in options or 'newautomountinformation' in options:
- new_key = options.get('rename', key)
- new_info = options.get('newautomountinformation', info)
-
- if new_key == DIRECT_MAP_KEY and not new_info:
- # automountinformation attribute of existing LDAP object needs
- # to be retrieved so that RDN can be generated
- dn = self.obj.get_dn(*keys, **options)
- entry_attrs_ = ldap.get_entry(dn, ['automountinformation'])
- new_info = entry_attrs_.get('automountinformation', [])[0]
-
- # automounkey attribute cannot be overwritten so that get_dn()
- # still works right
- options['newautomountkey'] = new_key
-
- new_rdn = self.obj.get_pk(new_key, new_info)
- if new_rdn != keys[-1]:
- options['rename'] = new_rdn
-
- result = super(automountkey_mod, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['automountkey'], options)
- return result
-
-
-@register()
-class automountkey_find(LDAPSearch):
- __doc__ = _('Search for an automount key.')
-
- msg_summary = ngettext(
- '%(count)d automount key matched',
- '%(count)d automount keys matched', 0
- )
-
-
-@register()
-class automountkey_show(LDAPRetrieve):
- __doc__ = _('Display an automount key.')
-
- takes_options = LDAPRetrieve.takes_options + (
- IA5Str('automountkey',
- cli_name='key',
- label=_('Key'),
- doc=_('Automount key name.'),
- ),
- IA5Str('automountinformation?',
- cli_name='info',
- label=_('Mount information'),
- ),
- )
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
-
- def execute(self, *keys, **options):
- keys += (self.obj.get_pk(options['automountkey'],
- options.get('automountinformation', None)), )
- options[self.obj.primary_key.name] = self.obj.get_pk(
- options['automountkey'],
- options.get('automountinformation', None))
-
- result = super(automountkey_show, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['automountkey'], options)
- return result
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
deleted file mode 100644
index bbd8ba146..000000000
--- a/ipalib/plugins/baseldap.py
+++ /dev/null
@@ -1,2397 +0,0 @@
-# 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/>.
-"""
-Base classes for LDAP plugins.
-"""
-
-import re
-import time
-from copy import deepcopy
-import base64
-
-import six
-
-from ipalib import api, crud, errors
-from ipalib import Method, Object
-from ipalib import Flag, Int, Str
-from ipalib.cli import to_cli
-from ipalib import output
-from ipalib.text import _
-from ipalib.util import json_serialize, validate_hostname
-from ipalib.capabilities import client_has_capability
-from ipalib.messages import add_message, SearchResultTruncated
-from ipapython.dn import DN
-from ipapython.version import API_VERSION
-
-if six.PY3:
- unicode = str
-
-DNA_MAGIC = -1
-
-global_output_params = (
- Flag('has_password',
- label=_('Password'),
- ),
- Str('member',
- label=_('Failed members'),
- ),
- Str('member_user?',
- label=_('Member users'),
- ),
- Str('member_group?',
- label=_('Member groups'),
- ),
- Str('memberof_group?',
- label=_('Member of groups'),
- ),
- Str('member_host?',
- label=_('Member hosts'),
- ),
- Str('member_hostgroup?',
- label=_('Member host-groups'),
- ),
- Str('memberof_hostgroup?',
- label=_('Member of host-groups'),
- ),
- Str('memberof_permission?',
- label=_('Permissions'),
- ),
- Str('memberof_privilege?',
- label='Privileges',
- ),
- Str('memberof_role?',
- label=_('Roles'),
- ),
- Str('memberof_sudocmdgroup?',
- label=_('Sudo Command Groups'),
- ),
- Str('member_privilege?',
- label='Granted to Privilege',
- ),
- Str('member_role?',
- label=_('Granting privilege to roles'),
- ),
- Str('member_netgroup?',
- label=_('Member netgroups'),
- ),
- Str('memberof_netgroup?',
- label=_('Member of netgroups'),
- ),
- Str('member_service?',
- label=_('Member services'),
- ),
- Str('member_servicegroup?',
- label=_('Member service groups'),
- ),
- Str('memberof_servicegroup?',
- label='Member of service groups',
- ),
- Str('member_hbacsvc?',
- label=_('Member HBAC service'),
- ),
- Str('member_hbacsvcgroup?',
- label=_('Member HBAC service groups'),
- ),
- Str('memberof_hbacsvcgroup?',
- label='Member of HBAC service groups',
- ),
- Str('member_sudocmd?',
- label='Member Sudo commands',
- ),
- Str('memberof_sudorule?',
- label='Member of Sudo rule',
- ),
- Str('memberof_hbacrule?',
- label='Member of HBAC rule',
- ),
- Str('memberindirect_user?',
- label=_('Indirect Member users'),
- ),
- Str('memberindirect_group?',
- label=_('Indirect Member groups'),
- ),
- Str('memberindirect_host?',
- label=_('Indirect Member hosts'),
- ),
- Str('memberindirect_hostgroup?',
- label=_('Indirect Member host-groups'),
- ),
- Str('memberindirect_role?',
- label=_('Indirect Member of roles'),
- ),
- Str('memberindirect_permission?',
- label=_('Indirect Member permissions'),
- ),
- Str('memberindirect_hbacsvc?',
- label=_('Indirect Member HBAC service'),
- ),
- Str('memberindirect_hbacsvcgrp?',
- label=_('Indirect Member HBAC service group'),
- ),
- Str('memberindirect_netgroup?',
- label=_('Indirect Member netgroups'),
- ),
- Str('memberofindirect_group?',
- label='Indirect Member of group',
- ),
- Str('memberofindirect_netgroup?',
- label='Indirect Member of netgroup',
- ),
- Str('memberofindirect_hostgroup?',
- label='Indirect Member of host-group',
- ),
- Str('memberofindirect_role?',
- label='Indirect Member of role',
- ),
- Str('memberofindirect_sudorule?',
- label='Indirect Member of Sudo rule',
- ),
- Str('memberofindirect_hbacrule?',
- label='Indirect Member of HBAC rule',
- ),
- Str('sourcehost',
- label=_('Failed source hosts/hostgroups'),
- ),
- Str('memberhost',
- label=_('Failed hosts/hostgroups'),
- ),
- Str('memberuser',
- label=_('Failed users/groups'),
- ),
- Str('memberservice',
- label=_('Failed service/service groups'),
- ),
- Str('failed',
- label=_('Failed to remove'),
- flags=['suppress_empty'],
- ),
- Str('ipasudorunas',
- label=_('Failed RunAs'),
- ),
- Str('ipasudorunasgroup',
- label=_('Failed RunAsGroup'),
- ),
-)
-
-
-def validate_add_attribute(ugettext, attr):
- validate_attribute(ugettext, 'addattr', attr)
-
-def validate_set_attribute(ugettext, attr):
- validate_attribute(ugettext, 'setattr', attr)
-
-def validate_del_attribute(ugettext, attr):
- validate_attribute(ugettext, 'delattr', attr)
-
-def validate_attribute(ugettext, name, attr):
- m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", attr)
- if not m or len(m.groups()) != 2:
- raise errors.ValidationError(
- name=name, error=_('Invalid format. Should be name=value'))
-
-def get_effective_rights(ldap, dn, attrs=None):
- assert isinstance(dn, DN)
- if attrs is None:
- attrs = ['*', 'nsaccountlock', 'cospriority']
- rights = ldap.get_effective_rights(dn, attrs)
- rdict = {}
- if 'attributelevelrights' in rights:
- rights = rights['attributelevelrights']
- rights = rights[0].split(', ')
- for r in rights:
- (k,v) = r.split(':')
- if v == 'none':
- # the string "none" means "no rights found"
- # see https://fedorahosted.org/freeipa/ticket/4359
- v = u''
- rdict[k.strip().lower()] = v
-
- return rdict
-
-def entry_from_entry(entry, newentry):
- """
- Python is more or less pass-by-value except for immutable objects. So if
- you pass in a dict to a function you are free to change members of that
- dict but you can't create a new dict in the function and expect to replace
- what was passed in.
-
- In some post-op plugins that is exactly what we want to do, so here is a
- clumsy way around the problem.
- """
-
- # Wipe out the current data
- for e in list(entry):
- del entry[e]
-
- # Re-populate it with new wentry
- for e in newentry.keys():
- entry[e] = newentry[e]
-
-def entry_to_dict(entry, **options):
- if options.get('raw', False):
- result = {}
- for attr in entry:
- if attr.lower() == 'attributelevelrights':
- value = entry[attr]
- elif entry.conn.get_attribute_type(attr) is bytes:
- value = entry.raw[attr]
- else:
- value = list(entry.raw[attr])
- for (i, v) in enumerate(value):
- try:
- value[i] = v.decode('utf-8')
- except UnicodeDecodeError:
- pass
- result[attr] = value
- else:
- result = dict((k.lower(), v) for (k, v) in entry.items())
- if options.get('all', False):
- result['dn'] = entry.dn
- return result
-
-def pkey_to_unicode(key):
- if key is None:
- key = []
- elif not isinstance(key, (tuple, list)):
- key = [key]
- key = u','.join(unicode(k) for k in key)
- return key
-
-def pkey_to_value(key, options):
- version = options.get('version', API_VERSION)
- if client_has_capability(version, 'primary_key_types'):
- return key
- return pkey_to_unicode(key)
-
-def wait_for_value(ldap, dn, attr, value):
- """
- 389-ds postoperation plugins are executed after the data has been
- returned to a client. This means that plugins that add data in a
- postop are not included in data returned to the user.
-
- The downside of waiting is that this increases the time of the
- command.
-
- The updated entry is returned.
- """
- # Loop a few times to give the postop-plugin a chance to complete
- # Don't sleep for more than 6 seconds.
- x = 0
- while x < 20:
- # sleep first because the first search, even on a quiet system,
- # almost always fails.
- time.sleep(.3)
- x = x + 1
-
- # FIXME: put a try/except around here? I think it is probably better
- # to just let the exception filter up to the caller.
- entry_attrs = ldap.get_entry(dn, ['*'])
- if attr in entry_attrs:
- if isinstance(entry_attrs[attr], (list, tuple)):
- values = [y.lower() for y in entry_attrs[attr]]
- if value.lower() in values:
- break
- else:
- if value.lower() == entry_attrs[attr].lower():
- break
-
- return entry_attrs
-
-
-def validate_externalhost(ugettext, hostname):
- try:
- validate_hostname(hostname, check_fqdn=False, allow_underscore=True)
- except ValueError as e:
- return unicode(e)
-
-
-external_host_param = Str('externalhost*', validate_externalhost,
- label=_('External host'),
- flags=['no_option'],
-)
-
-
-def add_external_pre_callback(membertype, ldap, dn, keys, options):
- """
- Pre callback to validate external members.
-
- This should be called by a command pre callback directly.
-
- membertype is the type of member
- """
- assert isinstance(dn, DN)
-
- # validate hostname with allowed underscore characters, non-fqdn
- # hostnames are allowed
- def validate_host(hostname):
- validate_hostname(hostname, check_fqdn=False, allow_underscore=True)
-
- if options.get(membertype):
- if membertype == 'host':
- validator = validate_host
- else:
- param = api.Object[membertype].primary_key
-
- def validator(value):
- value = param(value)
- param.validate(value)
-
- for value in options[membertype]:
- try:
- validator(value)
- except errors.ValidationError as e:
- raise errors.ValidationError(name=membertype, error=e.error)
- except ValueError as e:
- raise errors.ValidationError(name=membertype, error=e)
- return dn
-
-
-def add_external_post_callback(ldap, dn, entry_attrs, failed, completed,
- memberattr, membertype, externalattr,
- normalize=True):
- """
- Takes the following arguments:
- failed - the list of failed entries, these are candidates for possible
- external entries to add
- completed - the number of successfully added entries so far
- memberattr - the attribute name that IPA uses for membership natively
- (e.g. memberhost)
- membertype - the object type of the member (e.g. host)
- externalattr - the attribute name that IPA uses to store the membership
- of the entries that are not managed by IPA
- (e.g externalhost)
-
- Returns the number of completed entries so far (the number of entries
- handled by IPA incremented by the number of handled external entries) and
- dn.
- """
- assert isinstance(dn, DN)
-
- completed_external = 0
-
- # Sift through the failures. We assume that these are all
- # entries that aren't stored in IPA, aka external entries.
- if memberattr in failed and membertype in failed[memberattr]:
- entry_attrs_ = ldap.get_entry(dn, [externalattr])
- dn = entry_attrs_.dn
- members = entry_attrs.get(memberattr, [])
- external_entries = entry_attrs_.get(externalattr, [])
- lc_external_entries = set(e.lower() for e in external_entries)
-
- failed_entries = []
- for entry in failed[memberattr][membertype]:
- membername = entry[0].lower()
- member_dn = api.Object[membertype].get_dn(membername)
- assert isinstance(member_dn, DN)
-
- if (membername not in lc_external_entries and
- member_dn not in members):
- # Not an IPA entry, assume external
- if normalize:
- external_entries.append(membername)
- else:
- external_entries.append(entry[0])
- lc_external_entries.add(membername)
- completed_external += 1
- elif (membername in lc_external_entries and
- member_dn not in members):
- # Already an external member, reset the error message
- msg = unicode(errors.AlreadyGroupMember().message)
- newerror = (entry[0], msg)
- ind = failed[memberattr][membertype].index(entry)
- failed[memberattr][membertype][ind] = newerror
- failed_entries.append(membername)
- else:
- # Really a failure
- failed_entries.append(membername)
-
- if completed_external:
- entry_attrs_[externalattr] = external_entries
- try:
- ldap.update_entry(entry_attrs_)
- except errors.EmptyModlist:
- pass
- failed[memberattr][membertype] = failed_entries
- entry_attrs[externalattr] = external_entries
-
- return (completed + completed_external, dn)
-
-
-def remove_external_post_callback(ldap, dn, entry_attrs, failed, completed,
- memberattr, membertype, externalattr):
- """
- Takes the following arguments:
- failed - the list of failed entries, these are candidates for possible
- external entries to remove
- completed - the number of successfully removed entries so far
- memberattr - the attribute name that IPA uses for membership natively
- (e.g. memberhost)
- membertype - the object type of the member (e.g. host)
- externalattr - the attribute name that IPA uses to store the membership
- of the entries that are not managed by IPA
- (e.g externalhost)
-
- Returns the number of completed entries so far (the number of entries
- handled by IPA incremented by the number of handled external entries) and
- dn.
- """
-
- assert isinstance(dn, DN)
-
- # Run through the failures and gracefully remove any member defined
- # as an external member.
- completed_external = 0
- if memberattr in failed and membertype in failed[memberattr]:
- entry_attrs_ = ldap.get_entry(dn, [externalattr])
- dn = entry_attrs_.dn
- external_entries = entry_attrs_.get(externalattr, [])
- failed_entries = []
-
- for entry in failed[memberattr][membertype]:
- membername = entry[0].lower()
- if membername in external_entries or entry[0] in external_entries:
- try:
- external_entries.remove(membername)
- except ValueError:
- external_entries.remove(entry[0])
- completed_external += 1
- else:
- msg = unicode(errors.NotGroupMember().message)
- newerror = (entry[0], msg)
- ind = failed[memberattr][membertype].index(entry)
- failed[memberattr][membertype][ind] = newerror
- failed_entries.append(membername)
-
- if completed_external:
- entry_attrs_[externalattr] = external_entries
- try:
- ldap.update_entry(entry_attrs_)
- except errors.EmptyModlist:
- pass
- failed[memberattr][membertype] = failed_entries
- entry_attrs[externalattr] = external_entries
-
- return (completed + completed_external, dn)
-
-
-def host_is_master(ldap, fqdn):
- """
- Check to see if this host is a master.
-
- Raises an exception if a master, otherwise returns nothing.
- """
- master_dn = DN(('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
- try:
- ldap.get_entry(master_dn, ['objectclass'])
- raise errors.ValidationError(name='hostname', error=_('An IPA master host cannot be deleted or disabled'))
- except errors.NotFound:
- # Good, not a master
- return
-
-
-def add_missing_object_class(ldap, objectclass, dn, entry_attrs=None, update=True):
- """
- Add object class if missing into entry. Fetches entry if not passed. Updates
- the entry by default.
-
- Returns the entry
- """
-
- if not entry_attrs:
- entry_attrs = ldap.get_entry(dn, ['objectclass'])
- if (objectclass.lower() not in (o.lower() for o in entry_attrs['objectclass'])):
- entry_attrs['objectclass'].append(objectclass)
- if update:
- ldap.update_entry(entry_attrs)
- return entry_attrs
-
-
-class LDAPObject(Object):
- """
- Object representing a LDAP entry.
- """
- backend_name = 'ldap2'
-
- parent_object = ''
- container_dn = ''
- object_name = _('entry')
- object_name_plural = _('entries')
- object_class = []
- object_class_config = None
- # If an objectclass is possible but not default in an entry. Needed for
- # collecting attributes for ACI UI.
- possible_objectclasses = []
- limit_object_classes = [] # Only attributes in these are allowed
- disallow_object_classes = [] # Disallow attributes in these
- permission_filter_objectclasses = None
- search_attributes = []
- search_attributes_config = None
- default_attributes = []
- search_display_attributes = [] # attributes displayed in LDAPSearch
- hidden_attributes = ['objectclass', 'aci']
- # set rdn_attribute only if RDN attribute differs from primary key!
- rdn_attribute = ''
- uuid_attribute = ''
- attribute_members = {}
- rdn_is_primary_key = False # Do we need RDN change to do a rename?
- password_attributes = []
- # Can bind as this entry (has userPassword or krbPrincipalKey)
- bindable = False
- relationships = {
- # attribute: (label, inclusive param prefix, exclusive param prefix)
- 'member': ('Member', '', 'no_'),
- 'memberof': ('Member Of', 'in_', 'not_in_'),
- 'memberindirect': (
- 'Indirect Member', None, 'no_indirect_'
- ),
- 'memberofindirect': (
- 'Indirect Member Of', None, 'not_in_indirect_'
- ),
- }
- label = _('Entry')
- label_singular = _('Entry')
- managed_permissions = {}
-
- container_not_found_msg = _('container entry (%(container)s) not found')
- parent_not_found_msg = _('%(parent)s: %(oname)s not found')
- object_not_found_msg = _('%(pkey)s: %(oname)s not found')
- already_exists_msg = _('%(oname)s with name "%(pkey)s" already exists')
-
- def get_dn(self, *keys, **kwargs):
- if self.parent_object:
- parent_dn = self.api.Object[self.parent_object].get_dn(*keys[:-1])
- else:
- parent_dn = DN(self.container_dn, api.env.basedn)
- if self.rdn_attribute:
- try:
- entry_attrs = self.backend.find_entry_by_attr(
- self.primary_key.name, keys[-1], self.object_class, [''],
- DN(self.container_dn, api.env.basedn)
- )
- except errors.NotFound:
- pass
- else:
- return entry_attrs.dn
- if self.primary_key and keys[-1] is not None:
- return self.backend.make_dn_from_attr(
- self.primary_key.name, keys[-1], parent_dn
- )
- assert isinstance(parent_dn, DN)
- return parent_dn
-
- def get_dn_if_exists(self, *keys, **kwargs):
- dn = self.get_dn(*keys, **kwargs)
- entry = self.backend.get_entry(dn, [''])
- return entry.dn
-
- def get_primary_key_from_dn(self, dn):
- assert isinstance(dn, DN)
- try:
- if self.rdn_attribute:
- entry_attrs = self.backend.get_entry(
- dn, [self.primary_key.name]
- )
- try:
- return entry_attrs[self.primary_key.name][0]
- except (KeyError, IndexError):
- return ''
- except errors.NotFound:
- pass
- try:
- return dn[self.primary_key.name]
- except KeyError:
- # The primary key is not in the DN.
- # This shouldn't happen, but we don't want a "show" command to
- # crash.
- # Just return the entire DN, it's all we have if the entry
- # doesn't exist
- return unicode(dn)
-
- def get_ancestor_primary_keys(self):
- if self.parent_object:
- parent_obj = self.api.Object[self.parent_object]
- for key in parent_obj.get_ancestor_primary_keys():
- yield key
- if parent_obj.primary_key:
- pkey = parent_obj.primary_key
- yield pkey.clone_rename(
- parent_obj.name + pkey.name, required=True, query=True,
- cli_name=parent_obj.name, label=pkey.label
- )
-
- def has_objectclass(self, classes, objectclass):
- oc = [x.lower() for x in classes]
- return objectclass.lower() in oc
-
- def convert_attribute_members(self, entry_attrs, *keys, **options):
- if options.get('raw', False):
- return
-
- container_dns = {}
- new_attrs = {}
-
- for attr in self.attribute_members:
- try:
- value = entry_attrs.raw[attr]
- except KeyError:
- continue
- del entry_attrs[attr]
-
- for member in value:
- memberdn = DN(member)
- for ldap_obj_name in self.attribute_members[attr]:
- ldap_obj = self.api.Object[ldap_obj_name]
- try:
- container_dn = container_dns[ldap_obj_name]
- except KeyError:
- container_dn = DN(ldap_obj.container_dn, api.env.basedn)
- container_dns[ldap_obj_name] = container_dn
-
- if memberdn.endswith(container_dn):
- new_value = ldap_obj.get_primary_key_from_dn(memberdn)
- new_attr_name = '%s_%s' % (attr, ldap_obj.name)
- try:
- new_attr = new_attrs[new_attr_name]
- except KeyError:
- new_attr = entry_attrs.setdefault(new_attr_name, [])
- new_attrs[new_attr_name] = new_attr
- new_attr.append(new_value)
- break
-
- def get_indirect_members(self, entry_attrs, attrs_list):
- if 'memberindirect' in attrs_list:
- self.get_memberindirect(entry_attrs)
- if 'memberofindirect' in attrs_list:
- self.get_memberofindirect(entry_attrs)
-
- def get_memberindirect(self, group_entry):
- """
- Get indirect members
- """
-
- mo_filter = self.backend.make_filter({'memberof': group_entry.dn})
- filter = self.backend.combine_filters(
- ('(member=*)', mo_filter), self.backend.MATCH_ALL)
- try:
- result = self.backend.get_entries(
- self.api.env.basedn,
- filter=filter,
- attrs_list=['member'],
- size_limit=-1, # paged search will get everything anyway
- paged_search=True)
- except errors.NotFound:
- result = []
-
- indirect = set()
- for entry in result:
- indirect.update(entry.raw.get('member', []))
- indirect.difference_update(group_entry.raw.get('member', []))
-
- if indirect:
- group_entry.raw['memberindirect'] = list(indirect)
-
- def get_memberofindirect(self, entry):
-
- dn = entry.dn
- filter = self.backend.make_filter(
- {'member': dn, 'memberuser': dn, 'memberhost': dn})
- try:
- result = self.backend.get_entries(
- self.api.env.basedn,
- filter=filter,
- attrs_list=[''])
- except errors.NotFound:
- result = []
-
- direct = set()
- indirect = set(entry.raw.get('memberof', []))
- for group_entry in result:
- dn = str(group_entry.dn)
- if dn in indirect:
- indirect.remove(dn)
- direct.add(dn)
-
- entry.raw['memberof'] = list(direct)
- if indirect:
- entry.raw['memberofindirect'] = list(indirect)
-
- def get_password_attributes(self, ldap, dn, entry_attrs):
- """
- Search on the entry to determine if it has a password or
- keytab set.
-
- A tuple is used to determine which attribute is set
- in entry_attrs. The value is set to True/False whether a
- given password type is set.
- """
- for (pwattr, attr) in self.password_attributes:
- search_filter = '(%s=*)' % pwattr
- try:
- (entries, truncated) = ldap.find_entries(
- search_filter, [pwattr], dn, ldap.SCOPE_BASE
- )
- entry_attrs[attr] = True
- except errors.NotFound:
- entry_attrs[attr] = False
-
- def handle_not_found(self, *keys):
- pkey = ''
- if self.primary_key:
- pkey = keys[-1]
- raise errors.NotFound(
- reason=self.object_not_found_msg % {
- 'pkey': pkey, 'oname': self.object_name,
- }
- )
-
- def handle_duplicate_entry(self, *keys):
- try:
- pkey = keys[-1]
- except IndexError:
- pkey = ''
- raise errors.DuplicateEntry(
- message=self.already_exists_msg % {
- 'pkey': pkey, 'oname': self.object_name,
- }
- )
-
- # list of attributes we want exported to JSON
- json_friendly_attributes = (
- 'parent_object', 'container_dn', 'object_name', 'object_name_plural',
- 'object_class', 'object_class_config', 'default_attributes', 'label', 'label_singular',
- 'hidden_attributes', 'uuid_attribute', 'attribute_members', 'name',
- 'takes_params', 'rdn_attribute', 'bindable', 'relationships',
- )
-
- def __json__(self):
- ldap = self.backend
- json_dict = dict(
- (a, json_serialize(getattr(self, a))) for a in self.json_friendly_attributes
- )
- if self.primary_key:
- json_dict['primary_key'] = self.primary_key.name
- objectclasses = self.object_class
- if self.object_class_config:
- config = ldap.get_ipa_config()
- objectclasses = config.get(
- self.object_class_config, objectclasses
- )
- objectclasses = objectclasses + self.possible_objectclasses
- # Get list of available attributes for this object for use
- # in the ACI UI.
- attrs = self.api.Backend.ldap2.schema.attribute_types(objectclasses)
- attrlist = []
- # Go through the MUST first
- for (oid, attr) in attrs[0].items():
- attrlist.append(attr.names[0].lower())
- # And now the MAY
- for (oid, attr) in attrs[1].items():
- attrlist.append(attr.names[0].lower())
- json_dict['aciattrs'] = attrlist
- attrlist.sort()
- json_dict['methods'] = [m for m in self.methods]
- json_dict['can_have_permissions'] = bool(
- self.permission_filter_objectclasses)
- return json_dict
-
-
-# addattr can cause parameters to have more than one value even if not defined
-# as multivalue, make sure this isn't the case
-def _check_single_value_attrs(params, entry_attrs):
- for (a, v) in entry_attrs.items():
- if isinstance(v, (list, tuple)) and len(v) > 1:
- if a in params and not params[a].multivalue:
- raise errors.OnlyOneValueAllowed(attr=a)
-
-# setattr or --option='' can cause parameters to be empty that are otherwise
-# required, make sure we enforce that.
-def _check_empty_attrs(params, entry_attrs):
- for (a, v) in entry_attrs.items():
- if v is None or (isinstance(v, six.string_types) and len(v) == 0):
- if a in params and params[a].required:
- raise errors.RequirementError(name=a)
-
-
-def _check_limit_object_class(attributes, attrs, allow_only):
- """
- If the set of objectclasses is limited enforce that only those
- are updated in entry_attrs (plus dn)
-
- allow_only tells us what mode to check in:
-
- If True then we enforce that the attributes must be in the list of
- allowed.
-
- If False then those attributes are not allowed.
- """
- if len(attributes[0]) == 0 and len(attributes[1]) == 0:
- return
- limitattrs = deepcopy(attrs)
- # Go through the MUST first
- for (oid, attr) in attributes[0].items():
- if attr.names[0].lower() in limitattrs:
- if not allow_only:
- raise errors.ObjectclassViolation(
- info=_('attribute "%(attribute)s" not allowed') % dict(
- attribute=attr.names[0].lower()))
- limitattrs.remove(attr.names[0].lower())
- # And now the MAY
- for (oid, attr) in attributes[1].items():
- if attr.names[0].lower() in limitattrs:
- if not allow_only:
- raise errors.ObjectclassViolation(
- info=_('attribute "%(attribute)s" not allowed') % dict(
- attribute=attr.names[0].lower()))
- limitattrs.remove(attr.names[0].lower())
- if len(limitattrs) > 0 and allow_only:
- raise errors.ObjectclassViolation(
- info=_('attribute "%(attribute)s" not allowed') % dict(
- attribute=limitattrs[0]))
-
-
-class BaseLDAPCommand(Method):
- """
- Base class for Base LDAP Commands.
- """
- setattr_option = Str('setattr*', validate_set_attribute,
- cli_name='setattr',
- doc=_("""Set an attribute to a name/value pair. Format is attr=value.
-For multi-valued attributes, the command replaces the values already present."""),
- exclude='webui',
- )
- addattr_option = Str('addattr*', validate_add_attribute,
- cli_name='addattr',
- doc=_("""Add an attribute/value pair. Format is attr=value. The attribute
-must be part of the schema."""),
- exclude='webui',
- )
- delattr_option = Str('delattr*', validate_del_attribute,
- cli_name='delattr',
- doc=_("""Delete an attribute/value pair. The option will be evaluated
-last, after all sets and adds."""),
- exclude='webui',
- )
-
- callback_types = Method.callback_types + ('pre',
- 'post',
- 'exc')
-
- def get_summary_default(self, output):
- if 'value' in output:
- output = dict(output)
- output['value'] = pkey_to_unicode(output['value'])
- return super(BaseLDAPCommand, self).get_summary_default(output)
-
- def _convert_2_dict(self, ldap, attrs):
- """
- Convert a string in the form of name/value pairs into a dictionary.
-
- :param attrs: A list of name/value pair strings, in the "name=value"
- format. May also be a single string, or None.
- """
-
- newdict = {}
- if attrs is None:
- attrs = []
- elif not type(attrs) in (list, tuple):
- attrs = [attrs]
- for a in attrs:
- m = re.match("\s*(.*?)\s*=\s*(.*?)\s*$", a)
- attr = str(m.group(1)).lower()
- value = m.group(2)
- if attr in self.obj.params and attr not in self.params:
- # The attribute is managed by IPA, but it didn't get cloned
- # to the command. This happens with no_update/no_create attrs.
- raise errors.ValidationError(
- name=attr, error=_('attribute is not configurable'))
- if len(value) == 0:
- # None means "delete this attribute"
- value = None
-
- if attr in newdict:
- if type(value) in (tuple,):
- newdict[attr] += list(value)
- else:
- newdict[attr].append(value)
- else:
- if type(value) in (tuple,):
- newdict[attr] = list(value)
- else:
- newdict[attr] = [value]
- return newdict
-
- def process_attr_options(self, entry_attrs, dn, keys, options):
- """
- Process all --setattr, --addattr, and --delattr options and add the
- resulting value to the list of attributes. --setattr is processed first,
- then --addattr and finally --delattr.
-
- When --setattr is not used then the original LDAP object is looked up
- (of course, not when dn is None) and the changes are applied to old
- object values.
-
- Attribute values deleted by --delattr may be deleted from attribute
- values set or added by --setattr, --addattr. For example, the following
- attributes will result in a NOOP:
-
- --addattr=attribute=foo --delattr=attribute=foo
-
- AttrValueNotFound exception may be raised when an attribute value was
- not found either by --setattr and --addattr nor in existing LDAP object.
-
- :param entry_attrs: A list of attributes that will be updated
- :param dn: dn of updated LDAP object or None if a new object is created
- :param keys: List of command arguments
- :param options: List of options
- """
-
- if all(k not in options for k in ("setattr", "addattr", "delattr")):
- return
-
- ldap = self.obj.backend
-
- adddict = self._convert_2_dict(ldap, options.get('addattr', []))
- setdict = self._convert_2_dict(ldap, options.get('setattr', []))
- deldict = self._convert_2_dict(ldap, options.get('delattr', []))
-
- setattrs = set(setdict)
- addattrs = set(adddict)
- delattrs = set(deldict)
-
- if dn is None:
- direct_add = addattrs
- direct_del = delattrs
- needldapattrs = []
- else:
- assert isinstance(dn, DN)
- direct_add = setattrs & addattrs
- direct_del = setattrs & delattrs
- needldapattrs = list((addattrs | delattrs) - setattrs)
-
- for attr, val in setdict.items():
- entry_attrs[attr] = val
-
- for attr in direct_add:
- try:
- val = entry_attrs[attr]
- except KeyError:
- val = []
- else:
- if not isinstance(val, (list, tuple)):
- val = [val]
- elif isinstance(val, tuple):
- val = list(val)
- val.extend(adddict[attr])
- entry_attrs[attr] = val
-
- for attr in direct_del:
- for delval in deldict[attr]:
- try:
- entry_attrs[attr].remove(delval)
- except ValueError:
- raise errors.AttrValueNotFound(attr=attr, value=delval)
-
- if needldapattrs:
- try:
- old_entry = self._exc_wrapper(keys, options, ldap.get_entry)(
- dn, needldapattrs
- )
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- # Provide a nice error message when user tries to delete an
- # attribute that does not exist on the entry (and user is not
- # adding it)
- names = set(n.lower() for n in old_entry)
- del_nonexisting = delattrs - (names | setattrs | addattrs)
- if del_nonexisting:
- raise errors.ValidationError(name=del_nonexisting.pop(),
- error=_('No such attribute on this entry'))
-
- for attr in needldapattrs:
- entry_attrs[attr] = old_entry.get(attr, [])
-
- if attr in addattrs:
- entry_attrs[attr].extend(adddict.get(attr, []))
-
- for delval in deldict.get(attr, []):
- try:
- entry_attrs[attr].remove(delval)
- except ValueError:
- if isinstance(delval, bytes):
- # This is a Binary value, base64 encode it
- delval = unicode(base64.b64encode(delval))
- raise errors.AttrValueNotFound(attr=attr, value=delval)
-
- # normalize all values
- changedattrs = setattrs | addattrs | delattrs
- for attr in changedattrs:
- if attr in self.params and self.params[attr].attribute:
- # convert single-value params to scalars
- param = self.params[attr]
- value = entry_attrs[attr]
- if not param.multivalue:
- if len(value) == 1:
- value = value[0]
- elif not value:
- value = None
- else:
- raise errors.OnlyOneValueAllowed(attr=attr)
- # validate, convert and encode params
- try:
- value = param(value)
- param.validate(value)
- except errors.ValidationError as err:
- raise errors.ValidationError(name=attr, error=err.error)
- except errors.ConversionError as err:
- raise errors.ConversionError(name=attr, error=err.error)
- if isinstance(value, tuple):
- value = list(value)
- entry_attrs[attr] = value
- else:
- # unknown attribute: remove duplicite and invalid values
- entry_attrs[attr] = list(set([val for val in entry_attrs[attr] if val]))
- if not entry_attrs[attr]:
- entry_attrs[attr] = None
- elif isinstance(entry_attrs[attr], (tuple, list)) and len(entry_attrs[attr]) == 1:
- entry_attrs[attr] = entry_attrs[attr][0]
-
- @classmethod
- def register_pre_callback(cls, callback, first=False):
- """Shortcut for register_callback('pre', ...)"""
- cls.register_callback('pre', callback, first)
-
- @classmethod
- def register_post_callback(cls, callback, first=False):
- """Shortcut for register_callback('post', ...)"""
- cls.register_callback('post', callback, first)
-
- @classmethod
- def register_exc_callback(cls, callback, first=False):
- """Shortcut for register_callback('exc', ...)"""
- cls.register_callback('exc', callback, first)
-
- def _exc_wrapper(self, keys, options, call_func):
- """Function wrapper that automatically calls exception callbacks"""
- def wrapped(*call_args, **call_kwargs):
- # call call_func first
- func = call_func
- callbacks = list(self.get_callbacks('exc'))
- while True:
- try:
- return func(*call_args, **call_kwargs)
- except errors.ExecutionError as exc:
- e = exc
- if not callbacks:
- raise
- # call exc_callback in the next loop
- callback = callbacks.pop(0)
- def exc_func(*args, **kwargs):
- return callback(
- self, keys, options, e, call_func, *args, **kwargs)
- func = exc_func
- return wrapped
-
- def get_options(self):
- for param in super(BaseLDAPCommand, self).get_options():
- yield param
- if self.obj.attribute_members:
- for o in self.has_output:
- if isinstance(o, (output.Entry, output.ListOfEntries)):
- yield Flag('no_members',
- doc=_('Suppress processing of membership attributes.'),
- exclude='webui',
- flags={'no_output'},
- )
- break
-
-class LDAPCreate(BaseLDAPCommand, crud.Create):
- """
- Create a new entry in LDAP.
- """
- takes_options = (BaseLDAPCommand.setattr_option, BaseLDAPCommand.addattr_option)
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
- for arg in super(LDAPCreate, self).get_args():
- yield arg
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- entry_attrs = ldap.make_entry(
- dn, self.args_options_2_entry(*keys, **options))
-
- self.process_attr_options(entry_attrs, None, keys, options)
-
- entry_attrs['objectclass'] = deepcopy(self.obj.object_class)
-
- if self.obj.object_class_config:
- config = ldap.get_ipa_config()
- entry_attrs['objectclass'] = config.get(
- self.obj.object_class_config, entry_attrs['objectclass']
- )
-
- if self.obj.uuid_attribute:
- entry_attrs[self.obj.uuid_attribute] = 'autogenerate'
-
- if self.obj.rdn_attribute:
- try:
- dn_attr = dn[0].attr
- except (IndexError, KeyError):
- dn_attr = None
- if dn_attr != self.obj.primary_key.name:
- self.obj.handle_duplicate_entry(*keys)
- entry_attrs.dn = ldap.make_dn(
- entry_attrs, self.obj.rdn_attribute,
- DN(self.obj.container_dn, api.env.basedn))
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- attrs_list.update(entry_attrs.keys())
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- for callback in self.get_callbacks('pre'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, attrs_list,
- *keys, **options)
-
- _check_single_value_attrs(self.params, entry_attrs)
- _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.limit_object_classes), list(entry_attrs), allow_only=True)
- _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.disallow_object_classes), list(entry_attrs), allow_only=False)
-
- try:
- self._exc_wrapper(keys, options, ldap.add_entry)(entry_attrs)
- except errors.NotFound:
- parent = self.obj.parent_object
- if parent:
- raise errors.NotFound(
- reason=self.obj.parent_not_found_msg % {
- 'parent': keys[-2],
- 'oname': self.api.Object[parent].object_name,
- }
- )
- raise errors.NotFound(
- reason=self.obj.container_not_found_msg % {
- 'container': self.obj.container_dn,
- }
- )
- except errors.DuplicateEntry:
- self.obj.handle_duplicate_entry(*keys)
-
- try:
- if self.obj.rdn_attribute:
- # make sure objectclass is either set or None
- if self.obj.object_class:
- object_class = self.obj.object_class
- else:
- object_class = None
- entry_attrs = self._exc_wrapper(keys, options, ldap.find_entry_by_attr)(
- self.obj.primary_key.name, keys[-1], object_class, attrs_list,
- DN(self.obj.container_dn, api.env.basedn)
- )
- else:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- entry_attrs.dn, attrs_list)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.obj.get_indirect_members(entry_attrs, attrs_list)
-
- for callback in self.get_callbacks('post'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, *keys, **options)
-
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- if self.obj.primary_key:
- pkey = keys[-1]
- else:
- pkey = None
-
- return dict(result=entry_attrs, value=pkey_to_value(pkey, options))
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPQuery(BaseLDAPCommand, crud.PKQuery):
- """
- Base class for commands that need to retrieve an existing entry.
- """
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
- for arg in super(LDAPQuery, self).get_args():
- yield arg
-
-
-class LDAPMultiQuery(LDAPQuery):
- """
- Base class for commands that need to retrieve one or more existing entries.
- """
- takes_options = (
- Flag('continue',
- cli_name='continue',
- doc=_('Continuous mode: Don\'t stop on errors.'),
- ),
- )
-
- def get_args(self):
- for arg in super(LDAPMultiQuery, self).get_args():
- if self.obj.primary_key and arg.name == self.obj.primary_key.name:
- yield arg.clone(multivalue=True)
- else:
- yield arg
-
-
-class LDAPRetrieve(LDAPQuery):
- """
- Retrieve an LDAP entry.
- """
- has_output = output.standard_entry
- has_output_params = global_output_params
-
- takes_options = (
- Flag('rights',
- label=_('Rights'),
- doc=_('Display the access rights of this entry (requires --all). See ipa man page for details.'),
- ),
- )
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- assert isinstance(dn, DN)
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, attrs_list, *keys, **options)
- assert isinstance(dn, DN)
-
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- dn, attrs_list
- )
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.obj.get_indirect_members(entry_attrs, attrs_list)
-
- if options.get('rights', False) and options.get('all', False):
- entry_attrs['attributelevelrights'] = get_effective_rights(
- ldap, entry_attrs.dn)
-
- for callback in self.get_callbacks('post'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, *keys, **options)
-
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- if self.obj.primary_key:
- pkey = keys[-1]
- else:
- pkey = None
-
- return dict(result=entry_attrs, value=pkey_to_value(pkey, options))
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPUpdate(LDAPQuery, crud.Update):
- """
- Update an LDAP entry.
- """
-
- takes_options = (
- BaseLDAPCommand.setattr_option,
- BaseLDAPCommand.addattr_option,
- BaseLDAPCommand.delattr_option,
- Flag('rights',
- label=_('Rights'),
- doc=_('Display the access rights of this entry (requires --all). See ipa man page for details.'),
- ),
- )
-
- has_output_params = global_output_params
-
- def _get_rename_option(self):
- rdnparam = getattr(self.obj.params, self.obj.primary_key.name)
- return rdnparam.clone_rename('rename',
- cli_name='rename', required=False, label=_('Rename'),
- doc=_('Rename the %(ldap_obj_name)s object') % dict(
- ldap_obj_name=self.obj.object_name
- )
- )
-
- def get_options(self):
- for option in super(LDAPUpdate, self).get_options():
- yield option
- if self.obj.rdn_is_primary_key:
- yield self._get_rename_option()
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- if len(options) == 2: # 'all' and 'raw' are always sent
- raise errors.EmptyModlist()
-
- dn = self.obj.get_dn(*keys, **options)
- entry_attrs = ldap.make_entry(dn, self.args_options_2_entry(**options))
-
- self.process_attr_options(entry_attrs, dn, keys, options)
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- attrs_list.update(entry_attrs.keys())
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- _check_single_value_attrs(self.params, entry_attrs)
- _check_empty_attrs(self.obj.params, entry_attrs)
-
- for callback in self.get_callbacks('pre'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, attrs_list,
- *keys, **options)
-
- _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.limit_object_classes), list(entry_attrs), allow_only=True)
- _check_limit_object_class(self.api.Backend.ldap2.schema.attribute_types(self.obj.disallow_object_classes), list(entry_attrs), allow_only=False)
-
- rdnupdate = False
- try:
- if self.obj.rdn_is_primary_key and 'rename' in options:
- if not options['rename']:
- raise errors.ValidationError(name='rename', error=u'can\'t be empty')
- entry_attrs[self.obj.primary_key.name] = options['rename']
-
- if self.obj.rdn_is_primary_key and self.obj.primary_key.name in entry_attrs:
- try:
- # RDN change
- new_dn = DN((self.obj.primary_key.name,
- entry_attrs[self.obj.primary_key.name]),
- *entry_attrs.dn[1:])
- self._exc_wrapper(keys, options, ldap.move_entry)(
- entry_attrs.dn,
- new_dn)
-
- rdnkeys = keys[:-1] + (entry_attrs[self.obj.primary_key.name], )
- entry_attrs.dn = self.obj.get_dn(*rdnkeys)
- options['rdnupdate'] = True
- rdnupdate = True
- except errors.EmptyModlist:
- # Attempt to rename to the current name, ignore
- pass
- finally:
- # Delete the primary_key from entry_attrs either way
- del entry_attrs[self.obj.primary_key.name]
-
- # Exception callbacks will need to test for options['rdnupdate']
- # to decide what to do. An EmptyModlist in this context doesn't
- # mean an error occurred, just that there were no other updates to
- # perform.
- update = self._exc_wrapper(keys, options, ldap.get_entry)(
- entry_attrs.dn, list(entry_attrs))
- update.update(entry_attrs)
-
- self._exc_wrapper(keys, options, ldap.update_entry)(update)
- except errors.EmptyModlist as e:
- if not rdnupdate:
- raise e
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- entry_attrs.dn, attrs_list)
- except errors.NotFound:
- raise errors.MidairCollision(
- format=_('the entry was deleted while being modified')
- )
-
- self.obj.get_indirect_members(entry_attrs, attrs_list)
-
- if options.get('rights', False) and options.get('all', False):
- entry_attrs['attributelevelrights'] = get_effective_rights(
- ldap, entry_attrs.dn)
-
- for callback in self.get_callbacks('post'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, *keys, **options)
-
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- if self.obj.primary_key:
- pkey = keys[-1]
- else:
- pkey = None
-
- return dict(result=entry_attrs, value=pkey_to_value(pkey, options))
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPDelete(LDAPMultiQuery):
- """
- Delete an LDAP entry and all of its direct subentries.
- """
- has_output = output.standard_multi_delete
-
- has_output_params = global_output_params
-
- subtree_delete = True
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- def delete_entry(pkey):
- nkeys = keys[:-1] + (pkey, )
- dn = self.obj.get_dn(*nkeys, **options)
- assert isinstance(dn, DN)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, *nkeys, **options)
- assert isinstance(dn, DN)
-
- def delete_subtree(base_dn):
- assert isinstance(base_dn, DN)
- truncated = True
- while truncated:
- try:
- (subentries, truncated) = ldap.find_entries(
- None, [''], base_dn, ldap.SCOPE_ONELEVEL
- )
- except errors.NotFound:
- break
- else:
- for entry_attrs in subentries:
- delete_subtree(entry_attrs.dn)
- try:
- self._exc_wrapper(nkeys, options, ldap.delete_entry)(base_dn)
- except errors.NotFound:
- self.obj.handle_not_found(*nkeys)
-
- try:
- self._exc_wrapper(nkeys, options, ldap.delete_entry)(dn)
- except errors.NotFound:
- self.obj.handle_not_found(*nkeys)
- except errors.NotAllowedOnNonLeaf:
- if not self.subtree_delete:
- raise
- # this entry is not a leaf entry, delete all child nodes
- delete_subtree(dn)
-
- for callback in self.get_callbacks('post'):
- result = callback(self, ldap, dn, *nkeys, **options)
-
- return result
-
- if self.obj.primary_key and isinstance(keys[-1], (list, tuple)):
- pkeyiter = keys[-1]
- elif keys[-1] is not None:
- pkeyiter = [keys[-1]]
- else:
- pkeyiter = []
-
- deleted = []
- failed = []
- for pkey in pkeyiter:
- try:
- delete_entry(pkey)
- except errors.ExecutionError:
- if not options.get('continue', False):
- raise
- failed.append(pkey)
- else:
- deleted.append(pkey)
- deleted = pkey_to_value(deleted, options)
- failed = pkey_to_value(failed, options)
-
- return dict(result=dict(failed=failed), value=deleted)
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- return True
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPModMember(LDAPQuery):
- """
- Base class for member manipulation.
- """
- member_attributes = ['member']
- member_param_doc = _('%s')
- member_param_label = _('member %s')
- member_count_out = ('%i member processed.', '%i members processed.')
-
- def get_options(self):
- for option in super(LDAPModMember, self).get_options():
- yield option
- for attr in self.member_attributes:
- for ldap_obj_name in self.obj.attribute_members[attr]:
- ldap_obj = self.api.Object[ldap_obj_name]
- name = to_cli(ldap_obj_name)
- doc = self.member_param_doc % ldap_obj.object_name_plural
- label = self.member_param_label % ldap_obj.object_name
- yield Str('%s*' % name, cli_name='%ss' % name, doc=doc,
- label=label, alwaysask=True)
-
- def get_member_dns(self, **options):
- dns = {}
- failed = {}
- for attr in self.member_attributes:
- dns[attr] = {}
- failed[attr] = {}
- for ldap_obj_name in self.obj.attribute_members[attr]:
- dns[attr][ldap_obj_name] = []
- failed[attr][ldap_obj_name] = []
- names = options.get(to_cli(ldap_obj_name), [])
- if not names:
- continue
- for name in names:
- if not name:
- continue
- ldap_obj = self.api.Object[ldap_obj_name]
- try:
- dns[attr][ldap_obj_name].append(ldap_obj.get_dn(name))
- except errors.PublicError as e:
- failed[attr][ldap_obj_name].append((name, unicode(e)))
- return (dns, failed)
-
-
-class LDAPAddMember(LDAPModMember):
- """
- Add other LDAP entries to members.
- """
- member_param_doc = _('%s to add')
- member_count_out = ('%i member added.', '%i members added.')
- allow_same = False
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of members added'),
- ),
- )
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- (member_dns, failed) = self.get_member_dns(**options)
-
- dn = self.obj.get_dn(*keys, **options)
- assert isinstance(dn, DN)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, member_dns, failed, *keys, **options)
- assert isinstance(dn, DN)
-
- completed = 0
- for (attr, objs) in member_dns.items():
- for ldap_obj_name in objs:
- for m_dn in member_dns[attr][ldap_obj_name]:
- assert isinstance(m_dn, DN)
- if not m_dn:
- continue
- try:
- ldap.add_entry_to_group(m_dn, dn, attr, allow_same=self.allow_same)
- except errors.PublicError as e:
- ldap_obj = self.api.Object[ldap_obj_name]
- failed[attr][ldap_obj_name].append((
- ldap_obj.get_primary_key_from_dn(m_dn),
- unicode(e),)
- )
- else:
- completed += 1
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- attrs_list.update(member_dns.keys())
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- dn, attrs_list
- )
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.obj.get_indirect_members(entry_attrs, attrs_list)
-
- for callback in self.get_callbacks('post'):
- (completed, entry_attrs.dn) = callback(
- self, ldap, completed, failed, entry_attrs.dn, entry_attrs,
- *keys, **options)
-
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- return dict(
- completed=completed,
- failed=failed,
- result=entry_attrs,
- )
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return (completed, dn)
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPRemoveMember(LDAPModMember):
- """
- Remove LDAP entries from members.
- """
- member_param_doc = _('%s to remove')
- member_count_out = ('%i member removed.', '%i members removed.')
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be removed'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of members removed'),
- ),
- )
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- (member_dns, failed) = self.get_member_dns(**options)
-
- dn = self.obj.get_dn(*keys, **options)
- assert isinstance(dn, DN)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, member_dns, failed, *keys, **options)
- assert isinstance(dn, DN)
-
- completed = 0
- for (attr, objs) in member_dns.items():
- for ldap_obj_name, m_dns in objs.items():
- for m_dn in m_dns:
- assert isinstance(m_dn, DN)
- if not m_dn:
- continue
- try:
- ldap.remove_entry_from_group(m_dn, dn, attr)
- except errors.PublicError as e:
- ldap_obj = self.api.Object[ldap_obj_name]
- failed[attr][ldap_obj_name].append((
- ldap_obj.get_primary_key_from_dn(m_dn),
- unicode(e),)
- )
- else:
- completed += 1
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- attrs_list.update(member_dns.keys())
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- # Give memberOf a chance to update entries
- time.sleep(.3)
-
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- dn, attrs_list
- )
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.obj.get_indirect_members(entry_attrs, attrs_list)
-
- for callback in self.get_callbacks('post'):
- (completed, entry_attrs.dn) = callback(
- self, ldap, completed, failed, entry_attrs.dn, entry_attrs,
- *keys, **options)
-
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- return dict(
- completed=completed,
- failed=failed,
- result=entry_attrs,
- )
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return (completed, dn)
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-def gen_pkey_only_option(cli_name):
- return Flag('pkey_only?',
- label=_('Primary key only'),
- doc=_('Results should contain primary key attribute only ("%s")') \
- % to_cli(cli_name),)
-
-class LDAPSearch(BaseLDAPCommand, crud.Search):
- """
- Retrieve all LDAP entries matching the given criteria.
- """
- member_attributes = []
- member_param_incl_doc = _('Search for %(searched_object)s with these %(relationship)s %(ldap_object)s.')
- member_param_excl_doc = _('Search for %(searched_object)s without these %(relationship)s %(ldap_object)s.')
-
- # LDAPSearch sorts all matched records in the end using their primary key
- # as a key attribute
- # Set the following attribute to False to turn sorting off
- sort_result_entries = True
-
- takes_options = (
- Int('timelimit?',
- label=_('Time Limit'),
- doc=_('Time limit of search in seconds (0 is unlimited)'),
- flags=['no_display'],
- minvalue=0,
- autofill=False,
- ),
- Int('sizelimit?',
- label=_('Size Limit'),
- doc=_('Maximum number of entries returned (0 is unlimited)'),
- flags=['no_display'],
- minvalue=0,
- autofill=False,
- ),
- )
-
- def get_args(self):
- for key in self.obj.get_ancestor_primary_keys():
- yield key
- for arg in super(LDAPSearch, self).get_args():
- yield arg
-
- def get_member_options(self, attr):
- for ldap_obj_name in self.obj.attribute_members[attr]:
- ldap_obj = self.api.Object[ldap_obj_name]
- relationship = self.obj.relationships.get(
- attr, ['member', '', 'no_']
- )
- doc = self.member_param_incl_doc % dict(
- searched_object=self.obj.object_name_plural,
- relationship=relationship[0].lower(),
- ldap_object=ldap_obj.object_name_plural
- )
- name = '%s%s' % (relationship[1], to_cli(ldap_obj_name))
- yield Str(
- '%s*' % name, cli_name='%ss' % name, doc=doc,
- label=ldap_obj.object_name
- )
- doc = self.member_param_excl_doc % dict(
- searched_object=self.obj.object_name_plural,
- relationship=relationship[0].lower(),
- ldap_object=ldap_obj.object_name_plural
- )
- name = '%s%s' % (relationship[2], to_cli(ldap_obj_name))
- yield Str(
- '%s*' % name, cli_name='%ss' % name, doc=doc,
- label=ldap_obj.object_name
- )
-
- def get_options(self):
- for option in super(LDAPSearch, self).get_options():
- if option.name == 'no_members':
- # no_members are always true for find commands, do not
- # show option in CLI but keep API compatibility
- option = option.clone(
- default=True, flags=option.flags | {"no_option"})
- yield option
- if self.obj.primary_key and \
- 'no_output' not in self.obj.primary_key.flags:
- yield gen_pkey_only_option(self.obj.primary_key.cli_name)
- for attr in self.member_attributes:
- for option in self.get_member_options(attr):
- yield option
-
- def get_member_filter(self, ldap, **options):
- filter = ''
- for attr in self.member_attributes:
- for ldap_obj_name in self.obj.attribute_members[attr]:
- ldap_obj = self.api.Object[ldap_obj_name]
- relationship = self.obj.relationships.get(
- attr, ['member', '', 'no_']
- )
- # Handle positive (MATCH_ALL) and negative (MATCH_NONE)
- # searches similarly
- param_prefixes = relationship[1:] # e.g. ('in_', 'not_in_')
- rules = ldap.MATCH_ALL, ldap.MATCH_NONE
- for param_prefix, rule in zip(param_prefixes, rules):
- param_name = '%s%s' % (param_prefix, to_cli(ldap_obj_name))
- if options.get(param_name):
- dns = []
- for pkey in options[param_name]:
- dns.append(ldap_obj.get_dn(pkey))
- flt = ldap.make_filter_from_attr(attr, dns, rule)
- filter = ldap.combine_filters(
- (filter, flt), ldap.MATCH_ALL
- )
- return filter
-
- has_output_params = global_output_params
-
- def execute(self, *args, **options):
- ldap = self.obj.backend
-
- index = tuple(self.args).index('criteria')
- keys = args[:index]
- try:
- term = args[index]
- except IndexError:
- term = None
- if self.obj.parent_object:
- base_dn = self.api.Object[self.obj.parent_object].get_dn(*keys)
- else:
- base_dn = DN(self.obj.container_dn, api.env.basedn)
- assert isinstance(base_dn, DN)
-
- search_kw = self.args_options_2_entry(**options)
-
- if self.obj.search_display_attributes:
- defattrs = self.obj.search_display_attributes
- else:
- defattrs = self.obj.default_attributes
-
- if options.get('pkey_only', False):
- attrs_list = [self.obj.primary_key.name]
- elif options.get('all', False):
- attrs_list = ['*'] + defattrs
- else:
- attrs_list = set(defattrs)
- attrs_list.update(search_kw.keys())
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- if self.obj.search_attributes:
- search_attrs = self.obj.search_attributes
- else:
- search_attrs = self.obj.default_attributes
- if self.obj.search_attributes_config:
- config = ldap.get_ipa_config()
- config_attrs = config.get(
- self.obj.search_attributes_config, [])
- if len(config_attrs) == 1 and (
- isinstance(config_attrs[0], six.string_types)):
- search_attrs = config_attrs[0].split(',')
-
- search_kw['objectclass'] = self.obj.object_class
- attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
-
- search_kw = {}
- for a in search_attrs:
- search_kw[a] = term
- term_filter = ldap.make_filter(search_kw, exact=False)
-
- member_filter = self.get_member_filter(ldap, **options)
-
- filter = ldap.combine_filters(
- (term_filter, attr_filter, member_filter), rules=ldap.MATCH_ALL
- )
-
- scope = ldap.SCOPE_ONELEVEL
- for callback in self.get_callbacks('pre'):
- (filter, base_dn, scope) = callback(
- self, ldap, filter, attrs_list, base_dn, scope, *args, **options)
- assert isinstance(base_dn, DN)
-
- try:
- (entries, truncated) = self._exc_wrapper(args, options, ldap.find_entries)(
- filter, attrs_list, base_dn, scope,
- time_limit=options.get('timelimit', None),
- size_limit=options.get('sizelimit', None)
- )
- except errors.EmptyResult:
- (entries, truncated) = ([], False)
- except errors.NotFound:
- self.api.Object[self.obj.parent_object].handle_not_found(*keys)
-
- for callback in self.get_callbacks('post'):
- truncated = callback(self, ldap, entries, truncated, *args, **options)
-
- if self.sort_result_entries:
- if self.obj.primary_key:
- def sort_key(x):
- return self.obj.primary_key.sort_key(
- x[self.obj.primary_key.name][0])
- entries.sort(key=sort_key)
-
- if not options.get('raw', False):
- for e in entries:
- self.obj.get_indirect_members(e, attrs_list)
- self.obj.convert_attribute_members(e, *args, **options)
-
- for (i, e) in enumerate(entries):
- entries[i] = entry_to_dict(e, **options)
- entries[i]['dn'] = e.dn
-
- result = dict(
- result=entries,
- count=len(entries),
- truncated=bool(truncated),
- )
-
- try:
- ldap.handle_truncated_result(truncated)
- except errors.LimitsExceeded as e:
- add_message(options['version'], result, SearchResultTruncated(
- reason=e))
-
- return result
-
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- return (filters, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- return truncated
-
- def exc_callback(self, args, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPModReverseMember(LDAPQuery):
- """
- Base class for reverse member manipulation.
- """
- reverse_attributes = ['member']
- reverse_param_doc = _('%s')
- reverse_count_out = ('%i member processed.', '%i members processed.')
-
- has_output_params = global_output_params
-
- def get_options(self):
- for option in super(LDAPModReverseMember, self).get_options():
- yield option
- for attr in self.reverse_attributes:
- for ldap_obj_name in self.obj.reverse_members[attr]:
- ldap_obj = self.api.Object[ldap_obj_name]
- name = to_cli(ldap_obj_name)
- doc = self.reverse_param_doc % ldap_obj.object_name_plural
- yield Str('%s*' % name, cli_name='%ss' % name, doc=doc,
- label=ldap_obj.object_name, alwaysask=True)
-
-
-class LDAPAddReverseMember(LDAPModReverseMember):
- """
- Add other LDAP entries to members in reverse.
-
- The call looks like "add A to B" but in fact executes
- add B to A to handle reverse membership.
- """
- member_param_doc = _('%s to add')
- member_count_out = ('%i member added.', '%i members added.')
-
- show_command = None
- member_command = None
- reverse_attr = None
- member_attr = None
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of members added'),
- ),
- )
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- # Ensure our target exists
- result = self.api.Command[self.show_command](keys[-1])['result']
- dn = result['dn']
- assert isinstance(dn, DN)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, *keys, **options)
- assert isinstance(dn, DN)
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- completed = 0
- failed = {'member': {self.reverse_attr: []}}
- for attr in options.get(self.reverse_attr) or []:
- try:
- options = {'%s' % self.member_attr: keys[-1]}
- try:
- result = self._exc_wrapper(keys, options, self.api.Command[self.member_command])(attr, **options)
- if result['completed'] == 1:
- completed = completed + 1
- else:
- failed['member'][self.reverse_attr].append((attr, result['failed']['member'][self.member_attr][0][1]))
- except errors.NotFound as e:
- msg = str(e)
- (attr, msg) = msg.split(':', 1)
- failed['member'][self.reverse_attr].append((attr, unicode(msg.strip())))
-
- except errors.PublicError as e:
- failed['member'][self.reverse_attr].append((attr, unicode(e)))
-
- # Update the member data.
- entry_attrs = ldap.get_entry(dn, ['*'])
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- for callback in self.get_callbacks('post'):
- (completed, entry_attrs.dn) = callback(
- self, ldap, completed, failed, entry_attrs.dn, entry_attrs,
- *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- return dict(
- completed=completed,
- failed=failed,
- result=entry_attrs,
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return (completed, dn)
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPRemoveReverseMember(LDAPModReverseMember):
- """
- Remove other LDAP entries from members in reverse.
-
- The call looks like "remove A from B" but in fact executes
- remove B from A to handle reverse membership.
- """
- member_param_doc = _('%s to remove')
- member_count_out = ('%i member removed.', '%i members removed.')
-
- show_command = None
- member_command = None
- reverse_attr = None
- member_attr = None
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be removed'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of members removed'),
- ),
- )
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- # Ensure our target exists
- result = self.api.Command[self.show_command](keys[-1])['result']
- dn = result['dn']
- assert isinstance(dn, DN)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, *keys, **options)
- assert isinstance(dn, DN)
-
- if options.get('all', False):
- attrs_list = ['*'] + self.obj.default_attributes
- else:
- attrs_list = set(self.obj.default_attributes)
- if options.get('no_members', False):
- attrs_list.difference_update(self.obj.attribute_members)
- attrs_list = list(attrs_list)
-
- completed = 0
- failed = {'member': {self.reverse_attr: []}}
- for attr in options.get(self.reverse_attr) or []:
- try:
- options = {'%s' % self.member_attr: keys[-1]}
- try:
- result = self._exc_wrapper(keys, options, self.api.Command[self.member_command])(attr, **options)
- if result['completed'] == 1:
- completed = completed + 1
- else:
- failed['member'][self.reverse_attr].append((attr, result['failed']['member'][self.member_attr][0][1]))
- except errors.NotFound as e:
- msg = str(e)
- (attr, msg) = msg.split(':', 1)
- failed['member'][self.reverse_attr].append((attr, unicode(msg.strip())))
-
- except errors.PublicError as e:
- failed['member'][self.reverse_attr].append((attr, unicode(e)))
-
- # Update the member data.
- entry_attrs = ldap.get_entry(dn, ['*'])
- self.obj.convert_attribute_members(entry_attrs, *keys, **options)
-
- for callback in self.get_callbacks('post'):
- (completed, entry_attrs.dn) = callback(
- self, ldap, completed, failed, entry_attrs.dn, entry_attrs,
- *keys, **options)
-
- dn = entry_attrs.dn
- entry_attrs = entry_to_dict(entry_attrs, **options)
- entry_attrs['dn'] = dn
-
- return dict(
- completed=completed,
- failed=failed,
- result=entry_attrs,
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return (completed, dn)
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- raise exc
-
-
-class LDAPModAttribute(LDAPQuery):
-
- attribute = None
-
- has_output = output.standard_entry
-
- def get_options(self):
- for option in super(LDAPModAttribute, self).get_options():
- yield option
-
- option = self.obj.params[self.attribute]
- attribute = 'virtual_attribute' not in option.flags
- yield option.clone(attribute=attribute, alwaysask=True)
-
- def _update_attrs(self, update, entry_attrs):
- raise NotImplementedError("%s.update_attrs()", self.__class__.__name__)
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- entry_attrs = ldap.make_entry(dn, self.args_options_2_entry(**options))
-
- if options.get('all', False):
- attrs_list = ['*', self.obj.primary_key.name]
- else:
- attrs_list = {self.obj.primary_key.name}
- attrs_list.update(entry_attrs.keys())
- attrs_list = list(attrs_list)
-
- for callback in self.get_callbacks('pre'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, attrs_list,
- *keys, **options)
-
- try:
- update = self._exc_wrapper(keys, options, ldap.get_entry)(
- entry_attrs.dn, list(entry_attrs))
- self._update_attrs(update, entry_attrs)
-
- self._exc_wrapper(keys, options, ldap.update_entry)(update)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(
- entry_attrs.dn, attrs_list)
- except errors.NotFound:
- raise errors.MidairCollision(
- format=_('the entry was deleted while being modified')
- )
-
- for callback in self.get_callbacks('post'):
- entry_attrs.dn = callback(
- self, ldap, entry_attrs.dn, entry_attrs, *keys, **options)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- if self.obj.primary_key:
- pkey = keys[-1]
- else:
- pkey = None
-
- return dict(result=entry_attrs, value=pkey_to_value(pkey, options))
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- assert isinstance(dn, DN)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args,
- **call_kwargs):
- raise exc
-
-
-class LDAPAddAttribute(LDAPModAttribute):
- msg_summary = _('added attribute value to entry %(value)')
-
- def _update_attrs(self, update, entry_attrs):
- for name, value in entry_attrs.items():
- old_value = set(update.get(name, []))
- value_to_add = set(value)
-
- if not old_value.isdisjoint(value_to_add):
- raise errors.ExecutionError(
- message=_('\'%s\' already contains one or more values'
- % name)
- )
-
- update[name] = list(old_value | value_to_add)
-
-
-class LDAPRemoveAttribute(LDAPModAttribute):
- msg_summary = _('removed attribute values from entry %(value)')
-
- def _update_attrs(self, update, entry_attrs):
- for name, value in entry_attrs.items():
- old_value = set(update.get(name, []))
- value_to_remove = set(value)
-
- if not value_to_remove.issubset(old_value):
- raise errors.AttrValueNotFound(
- attr=name, value=_("one or more values to remove"))
-
- update[name] = list(old_value - value_to_remove)
diff --git a/ipalib/plugins/baseuser.py b/ipalib/plugins/baseuser.py
deleted file mode 100644
index bbea403d9..000000000
--- a/ipalib/plugins/baseuser.py
+++ /dev/null
@@ -1,663 +0,0 @@
-# Authors:
-# Thierry Bordaz <tbordaz@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 string
-
-import six
-
-from ipalib import api, errors
-from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes
-from ipalib.plugable import Registry
-from .baseldap import (
- DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
- LDAPRetrieve, LDAPAddMember, LDAPRemoveMember)
-from .service import validate_certificate
-from ipalib.request import context
-from ipalib import _
-from ipapython.ipautil import ipa_generate_password
-from ipapython.ipavalidate import Email
-from ipalib.util import (
- normalize_sshpubkey,
- validate_sshpubkey,
- convert_sshpubkey_post,
- remove_sshpubkey_from_output_post,
- remove_sshpubkey_from_output_list_post,
- add_sshpubkey_to_attrs_pre,
-)
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Baseuser
-
-This contains common definitions for user/stageuser
-""")
-
-register = Registry()
-
-NO_UPG_MAGIC = '__no_upg__'
-
-baseuser_output_params = (
- Flag('has_keytab',
- label=_('Kerberos keys available'),
- ),
- Str('sshpubkeyfp*',
- label=_('SSH public key fingerprint'),
- ),
- )
-
-status_baseuser_output_params = (
- Str('server',
- label=_('Server'),
- ),
- Str('krbloginfailedcount',
- label=_('Failed logins'),
- ),
- Str('krblastsuccessfulauth',
- label=_('Last successful authentication'),
- ),
- Str('krblastfailedauth',
- label=_('Last failed authentication'),
- ),
- Str('now',
- label=_('Time now'),
- ),
- )
-
-UPG_DEFINITION_DN = DN(('cn', 'UPG Definition'),
- ('cn', 'Definitions'),
- ('cn', 'Managed Entries'),
- ('cn', 'etc'),
- api.env.basedn)
-
-# characters to be used for generating random user passwords
-baseuser_pwdchars = string.digits + string.ascii_letters + '_,.@+-='
-
-def validate_nsaccountlock(entry_attrs):
- if 'nsaccountlock' in entry_attrs:
- nsaccountlock = entry_attrs['nsaccountlock']
- if not isinstance(nsaccountlock, (bool, Bool)):
- if not isinstance(nsaccountlock, six.string_types):
- raise errors.OnlyOneValueAllowed(attr='nsaccountlock')
- if nsaccountlock.lower() not in ('true', 'false'):
- raise errors.ValidationError(name='nsaccountlock',
- error=_('must be TRUE or FALSE'))
-
-def radius_dn2pk(api, entry_attrs):
- cl = entry_attrs.get('ipatokenradiusconfiglink', None)
- if cl:
- pk = api.Object['radiusproxy'].get_primary_key_from_dn(cl[0])
- entry_attrs['ipatokenradiusconfiglink'] = [pk]
-
-def convert_nsaccountlock(entry_attrs):
- if not 'nsaccountlock' in entry_attrs:
- entry_attrs['nsaccountlock'] = False
- else:
- nsaccountlock = Bool('temp')
- entry_attrs['nsaccountlock'] = nsaccountlock.convert(entry_attrs['nsaccountlock'][0])
-
-def split_principal(principal):
- """
- Split the principal into its components and do some basic validation.
-
- Automatically append our realm if it wasn't provided.
- """
- realm = None
- parts = principal.split('@')
- user = parts[0].lower()
- if len(parts) > 2:
- raise errors.MalformedUserPrincipal(principal=principal)
-
- if len(parts) == 2:
- realm = parts[1].upper()
- # At some point we'll support multiple realms
- if realm != api.env.realm:
- raise errors.RealmMismatch()
- else:
- realm = api.env.realm
-
- return (user, realm)
-
-def validate_principal(ugettext, principal):
- """
- All the real work is done in split_principal.
- """
- (user, realm) = split_principal(principal)
- return None
-
-def normalize_principal(principal):
- """
- Ensure that the name in the principal is lower-case. The realm is
- upper-case by convention but it isn't required.
-
- The principal is validated at this point.
- """
- (user, realm) = split_principal(principal)
- return unicode('%s@%s' % (user, realm))
-
-
-
-def fix_addressbook_permission_bindrule(name, template, is_new,
- anonymous_read_aci,
- **other_options):
- """Fix bind rule type for Read User Addressbook/IPA Attributes permission
-
- When upgrading from an old IPA that had the global read ACI,
- or when installing the first replica with granular read permissions,
- we need to keep allowing anonymous access to many user attributes.
- This fixup_function changes the bind rule type accordingly.
- """
- if is_new and anonymous_read_aci:
- template['ipapermbindruletype'] = 'anonymous'
-
-
-
-class baseuser(LDAPObject):
- """
- baseuser object.
- """
-
- stage_container_dn = api.env.container_stageuser
- active_container_dn = api.env.container_user
- delete_container_dn = api.env.container_deleteuser
- object_class = ['posixaccount']
- object_class_config = 'ipauserobjectclasses'
- possible_objectclasses = [
- 'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
- 'ipatokenradiusproxyuser'
- ]
- disallow_object_classes = ['krbticketpolicyaux']
- permission_filter_objectclasses = ['posixaccount']
- search_attributes_config = 'ipausersearchfields'
- default_attributes = [
- 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
- 'uidnumber', 'gidnumber', 'mail', 'ou',
- 'telephonenumber', 'title', 'memberof', 'nsaccountlock',
- 'memberofindirect', 'ipauserauthtype', 'userclass',
- 'ipatokenradiusconfiglink', 'ipatokenradiususername',
- 'krbprincipalexpiration', 'usercertificate;binary',
- ]
- search_display_attributes = [
- 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
- 'mail', 'telephonenumber', 'title', 'nsaccountlock',
- 'uidnumber', 'gidnumber', 'sshpubkeyfp',
- ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'manager': ['user'],
- 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
- 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
- }
- rdn_is_primary_key = True
- bindable = True
- password_attributes = [('userpassword', 'has_password'),
- ('krbprincipalkey', 'has_keytab')]
- label = _('Users')
- label_singular = _('User')
-
- takes_params = (
- Str('uid',
- pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
- pattern_errmsg='may only include letters, numbers, _, -, . and $',
- maxlength=255,
- cli_name='login',
- label=_('User login'),
- primary_key=True,
- default_from=lambda givenname, sn: givenname[0] + sn,
- normalizer=lambda value: value.lower(),
- ),
- Str('givenname',
- cli_name='first',
- label=_('First name'),
- ),
- Str('sn',
- cli_name='last',
- label=_('Last name'),
- ),
- Str('cn',
- label=_('Full name'),
- default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
- autofill=True,
- ),
- Str('displayname?',
- label=_('Display name'),
- default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
- autofill=True,
- ),
- Str('initials?',
- label=_('Initials'),
- default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]),
- autofill=True,
- ),
- Str('homedirectory?',
- cli_name='homedir',
- label=_('Home directory'),
- ),
- Str('gecos?',
- label=_('GECOS'),
- default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
- autofill=True,
- ),
- Str('loginshell?',
- cli_name='shell',
- label=_('Login shell'),
- ),
- Str('krbprincipalname?', validate_principal,
- cli_name='principal',
- label=_('Kerberos principal'),
- default_from=lambda uid: '%s@%s' % (uid.lower(), api.env.realm),
- autofill=True,
- flags=['no_update'],
- normalizer=lambda value: normalize_principal(value),
- ),
- DateTime('krbprincipalexpiration?',
- cli_name='principal_expiration',
- label=_('Kerberos principal expiration'),
- ),
- Str('mail*',
- cli_name='email',
- label=_('Email address'),
- ),
- Password('userpassword?',
- cli_name='password',
- label=_('Password'),
- doc=_('Prompt to set the user password'),
- # FIXME: This is temporary till bug is fixed causing updates to
- # bomb out via the webUI.
- exclude='webui',
- ),
- Flag('random?',
- doc=_('Generate a random user password'),
- flags=('no_search', 'virtual_attribute'),
- default=False,
- ),
- Str('randompassword?',
- label=_('Random password'),
- flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
- ),
- Int('uidnumber?',
- cli_name='uid',
- label=_('UID'),
- doc=_('User ID Number (system will assign one if not provided)'),
- minvalue=1,
- ),
- Int('gidnumber?',
- label=_('GID'),
- doc=_('Group ID Number'),
- minvalue=1,
- ),
- Str('street?',
- cli_name='street',
- label=_('Street address'),
- ),
- Str('l?',
- cli_name='city',
- label=_('City'),
- ),
- Str('st?',
- cli_name='state',
- label=_('State/Province'),
- ),
- Str('postalcode?',
- label=_('ZIP'),
- ),
- Str('telephonenumber*',
- cli_name='phone',
- label=_('Telephone Number')
- ),
- Str('mobile*',
- label=_('Mobile Telephone Number')
- ),
- Str('pager*',
- label=_('Pager Number')
- ),
- Str('facsimiletelephonenumber*',
- cli_name='fax',
- label=_('Fax Number'),
- ),
- Str('ou?',
- cli_name='orgunit',
- label=_('Org. Unit'),
- ),
- Str('title?',
- label=_('Job Title'),
- ),
- # keep backward compatibility using single value manager option
- Str('manager?',
- label=_('Manager'),
- ),
- Str('carlicense*',
- label=_('Car License'),
- ),
- Str('ipasshpubkey*', validate_sshpubkey,
- cli_name='sshpubkey',
- label=_('SSH public key'),
- normalizer=normalize_sshpubkey,
- flags=['no_search'],
- ),
- StrEnum('ipauserauthtype*',
- cli_name='user_auth_type',
- label=_('User authentication types'),
- doc=_('Types of supported user authentication'),
- values=(u'password', u'radius', u'otp'),
- ),
- Str('userclass*',
- cli_name='class',
- label=_('Class'),
- doc=_('User category (semantics placed on this attribute are for '
- 'local interpretation)'),
- ),
- Str('ipatokenradiusconfiglink?',
- cli_name='radius',
- label=_('RADIUS proxy configuration'),
- ),
- Str('ipatokenradiususername?',
- cli_name='radius_username',
- label=_('RADIUS proxy username'),
- ),
- Str('departmentnumber*',
- label=_('Department Number'),
- ),
- Str('employeenumber?',
- label=_('Employee Number'),
- ),
- Str('employeetype?',
- label=_('Employee Type'),
- ),
- Str('preferredlanguage?',
- label=_('Preferred Language'),
- pattern='^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' \
- + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$',
- pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"',
- ),
- Bytes('usercertificate*', validate_certificate,
- cli_name='certificate',
- label=_('Certificate'),
- doc=_('Base-64 encoded user certificate'),
- ),
- )
-
- def normalize_and_validate_email(self, email, config=None):
- if not config:
- config = self.backend.get_ipa_config()
-
- # check if default email domain should be added
- defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
- if email:
- norm_email = []
- if not isinstance(email, (list, tuple)):
- email = [email]
- for m in email:
- if isinstance(m, six.string_types):
- if '@' not in m and defaultdomain:
- m = m + u'@' + defaultdomain
- if not Email(m):
- raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
- norm_email.append(m)
- else:
- if not Email(m):
- raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
- norm_email.append(m)
- return norm_email
-
- return email
-
- def normalize_manager(self, manager, container):
- """
- Given a userid verify the user's existence (in the appropriate containter) and return the dn.
- """
- if not manager:
- return None
-
- if not isinstance(manager, list):
- manager = [manager]
-
- try:
- container_dn = DN(container, api.env.basedn)
- for i, mgr in enumerate(manager):
- if isinstance(mgr, DN) and mgr.endswith(container_dn):
- continue
- entry_attrs = self.backend.find_entry_by_attr(
- self.primary_key.name, mgr, self.object_class, [''],
- container_dn
- )
- manager[i] = entry_attrs.dn
- except errors.NotFound:
- raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=mgr))
-
- return manager
-
- def _user_status(self, user, container):
- assert isinstance(user, DN)
- return user.endswith(container)
-
- def active_user(self, user):
- assert isinstance(user, DN)
- return self._user_status(user, DN(self.active_container_dn, api.env.basedn))
-
- def stage_user(self, user):
- assert isinstance(user, DN)
- return self._user_status(user, DN(self.stage_container_dn, api.env.basedn))
-
- def delete_user(self, user):
- assert isinstance(user, DN)
- return self._user_status(user, DN(self.delete_container_dn, api.env.basedn))
-
- def convert_usercertificate_pre(self, entry_attrs):
- if 'usercertificate' in entry_attrs:
- entry_attrs['usercertificate;binary'] = entry_attrs.pop(
- 'usercertificate')
-
- def convert_usercertificate_post(self, entry_attrs, **options):
- if 'usercertificate;binary' in entry_attrs:
- entry_attrs['usercertificate'] = entry_attrs.pop(
- 'usercertificate;binary')
-
- def convert_attribute_members(self, entry_attrs, *keys, **options):
- super(baseuser, self).convert_attribute_members(
- entry_attrs, *keys, **options)
-
- if options.get("raw", False):
- return
-
- # due the backward compatibility, managers have to be returned in
- # 'manager' attribute instead of 'manager_user'
- try:
- entry_attrs['failed_manager'] = entry_attrs.pop('manager')
- except KeyError:
- pass
-
- try:
- entry_attrs['manager'] = entry_attrs.pop('manager_user')
- except KeyError:
- pass
-
-
-class baseuser_add(LDAPCreate):
- """
- Prototype command plugin to be implemented by real plugin
- """
- def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- assert isinstance(dn, DN)
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- convert_sshpubkey_post(entry_attrs)
- radius_dn2pk(self.api, entry_attrs)
-
-class baseuser_del(LDAPDelete):
- """
- Prototype command plugin to be implemented by real plugin
- """
-
-class baseuser_mod(LDAPUpdate):
- """
- Prototype command plugin to be implemented by real plugin
- """
- def check_namelength(self, ldap, **options):
- if options.get('rename') is not None:
- config = ldap.get_ipa_config()
- if 'ipamaxusernamelength' in config:
- if len(options['rename']) > int(config.get('ipamaxusernamelength')[0]):
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('can be at most %(len)d characters') % dict(
- len = int(config.get('ipamaxusernamelength')[0])
- )
- )
- def check_mail(self, entry_attrs):
- if 'mail' in entry_attrs:
- entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'])
-
- def check_manager(self, entry_attrs, container):
- if 'manager' in entry_attrs:
- entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], container)
-
- def check_userpassword(self, entry_attrs, **options):
- if 'userpassword' not in entry_attrs and options.get('random'):
- entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
- # save the password so it can be displayed in post_callback
- setattr(context, 'randompassword', entry_attrs['userpassword'])
-
- def check_objectclass(self, ldap, dn, entry_attrs):
- if ('ipasshpubkey' in entry_attrs or 'ipauserauthtype' in entry_attrs
- or 'userclass' in entry_attrs or 'ipatokenradiusconfiglink' in entry_attrs):
- if 'objectclass' in entry_attrs:
- obj_classes = entry_attrs['objectclass']
- else:
- _entry_attrs = ldap.get_entry(dn, ['objectclass'])
- obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass']
-
- # IMPORTANT: compare objectclasses as case insensitive
- obj_classes = [o.lower() for o in obj_classes]
-
- if 'ipasshpubkey' in entry_attrs and 'ipasshuser' not in obj_classes:
- entry_attrs['objectclass'].append('ipasshuser')
-
- if 'ipauserauthtype' in entry_attrs and 'ipauserauthtypeclass' not in obj_classes:
- entry_attrs['objectclass'].append('ipauserauthtypeclass')
-
- if 'userclass' in entry_attrs and 'ipauser' not in obj_classes:
- entry_attrs['objectclass'].append('ipauser')
-
- if 'ipatokenradiusconfiglink' in entry_attrs:
- cl = entry_attrs['ipatokenradiusconfiglink']
- if cl:
- if 'ipatokenradiusproxyuser' not in obj_classes:
- entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
-
- answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
- entry_attrs['ipatokenradiusconfiglink'] = answer
-
- def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- assert isinstance(dn, DN)
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
-
- self.check_namelength(ldap, **options)
-
- self.check_mail(entry_attrs)
-
- self.check_manager(entry_attrs, self.obj.active_container_dn)
-
- self.check_userpassword(entry_attrs, **options)
-
- self.check_objectclass(ldap, dn, entry_attrs)
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if options.get('random', False):
- try:
- entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
- except AttributeError:
- # if both randompassword and userpassword options were used
- pass
- convert_nsaccountlock(entry_attrs)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- convert_sshpubkey_post(entry_attrs)
- remove_sshpubkey_from_output_post(self.context, entry_attrs)
- radius_dn2pk(self.api, entry_attrs)
-
-class baseuser_find(LDAPSearch):
- """
- Prototype command plugin to be implemented by real plugin
- """
- def args_options_2_entry(self, *args, **options):
- newoptions = {}
- self.common_enhance_options(newoptions, **options)
- options.update(newoptions)
-
- return super(baseuser_find, self).args_options_2_entry(
- *args, **options)
-
- def common_enhance_options(self, newoptions, **options):
- # assure the manager attr is a dn, not just a bare uid
- manager = options.get('manager')
- if manager is not None:
- newoptions['manager'] = self.obj.normalize_manager(manager, self.obj.active_container_dn)
-
- # Ensure that the RADIUS config link is a dn, not just the name
- cl = 'ipatokenradiusconfiglink'
- if cl in options:
- newoptions[cl] = self.api.Object['radiusproxy'].get_dn(options[cl])
-
- def pre_common_callback(self, ldap, filters, attrs_list, base_dn, scope,
- *args, **options):
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
-
- def post_common_callback(self, ldap, entries, lockout=False, **options):
- for attrs in entries:
- self.obj.convert_usercertificate_post(attrs, **options)
- if (lockout):
- attrs['nsaccountlock'] = True
- else:
- convert_nsaccountlock(attrs)
- convert_sshpubkey_post(attrs)
- remove_sshpubkey_from_output_list_post(self.context, entries)
-
-class baseuser_show(LDAPRetrieve):
- """
- Prototype command plugin to be implemented by real plugin
- """
- def pre_common_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
-
- def post_common_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- convert_sshpubkey_post(entry_attrs)
- remove_sshpubkey_from_output_post(self.context, entry_attrs)
- radius_dn2pk(self.api, entry_attrs)
-
-
-class baseuser_add_manager(LDAPAddMember):
- member_attributes = ['manager']
-
-
-class baseuser_remove_manager(LDAPRemoveMember):
- member_attributes = ['manager']
diff --git a/ipalib/plugins/batch.py b/ipalib/plugins/batch.py
deleted file mode 100644
index 84a650575..000000000
--- a/ipalib/plugins/batch.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Authors:
-# Adam Young <ayoung@redhat.com>
-# Rob Crittenden <rcritten@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/>.
-
-"""
-Plugin to make multiple ipa calls via one remote procedure call
-
-To run this code in the lite-server
-
-curl -H "Content-Type:application/json" -H "Accept:application/json" -H "Accept-Language:en" --negotiate -u : --cacert /etc/ipa/ca.crt -d @batch_request.json -X POST http://localhost:8888/ipa/json
-
-where the contents of the file batch_request.json follow the below example
-
-{"method":"batch","params":[[
- {"method":"group_find","params":[[],{}]},
- {"method":"user_find","params":[[],{"whoami":"true","all":"true"}]},
- {"method":"user_show","params":[["admin"],{"all":true}]}
- ],{}],"id":1}
-
-The format of the response is nested the same way. At the top you will see
- "error": null,
- "id": 1,
- "result": {
- "count": 3,
- "results": [
-
-
-And then a nested response for each IPA command method sent in the request
-
-"""
-
-import six
-
-from ipalib import api, errors
-from ipalib import Command
-from ipalib.parameters import Str, Any
-from ipalib.output import Output
-from ipalib.text import _
-from ipalib.request import context
-from ipalib.plugable import Registry
-from ipapython.version import API_VERSION
-
-if six.PY3:
- unicode = str
-
-register = Registry()
-
-@register()
-class batch(Command):
- NO_CLI = True
-
- takes_args = (
- Any('methods*',
- doc=_('Nested Methods to execute'),
- ),
- )
-
- take_options = (
- Str('version',
- cli_name='version',
- doc=_('Client version. Used to determine if server will accept request.'),
- exclude='webui',
- flags=['no_option', 'no_output'],
- default=API_VERSION,
- autofill=True,
- ),
- )
-
- has_output = (
- Output('count', int, doc=''),
- Output('results', (list, tuple), doc='')
- )
-
- def execute(self, methods=None, **options):
- results = []
- for arg in (methods or []):
- params = dict()
- name = None
- try:
- if 'method' not in arg:
- raise errors.RequirementError(name='method')
- if 'params' not in arg:
- raise errors.RequirementError(name='params')
- name = arg['method']
- if name not in self.Command:
- raise errors.CommandError(name=name)
- a, kw = arg['params']
- newkw = dict((str(k), v) for k, v in kw.items())
- params = api.Command[name].args_options_2_params(*a, **newkw)
- newkw.setdefault('version', options['version'])
-
- result = api.Command[name](*a, **newkw)
- self.info(
- '%s: batch: %s(%s): SUCCESS',
- getattr(context, 'principal', 'UNKNOWN'),
- name,
- ', '.join(api.Command[name]._repr_iter(**params))
- )
- result['error']=None
- except Exception as e:
- if isinstance(e, errors.RequirementError) or \
- isinstance(e, errors.CommandError):
- self.info(
- '%s: batch: %s',
- context.principal, # pylint: disable=no-member
- e.__class__.__name__
- )
- else:
- self.info(
- '%s: batch: %s(%s): %s',
- context.principal, name, # pylint: disable=no-member
- ', '.join(api.Command[name]._repr_iter(**params)),
- e.__class__.__name__
- )
- if isinstance(e, errors.PublicError):
- reported_error = e
- else:
- reported_error = errors.InternalError()
- result = dict(
- error=reported_error.strerror,
- error_code=reported_error.errno,
- error_name=unicode(type(reported_error).__name__),
- error_kw=reported_error.kw,
- )
- results.append(result)
- return dict(count=len(results) , results=results)
-
diff --git a/ipalib/plugins/caacl.py b/ipalib/plugins/caacl.py
deleted file mode 100644
index 60eeb5a33..000000000
--- a/ipalib/plugins/caacl.py
+++ /dev/null
@@ -1,562 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-import pyhbac
-
-from ipalib import api, errors, output
-from ipalib import Bool, Str, StrEnum
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete, LDAPQuery,
- LDAPUpdate, LDAPRetrieve, LDAPAddMember, LDAPRemoveMember,
- global_output_params, pkey_to_value)
-from .hbacrule import is_all
-from .service import normalize_principal, split_any_principal
-from ipalib import _, ngettext
-from ipapython.dn import DN
-
-
-__doc__ = _("""
-Manage CA ACL rules.
-
-This plugin is used to define rules governing which principals are
-permitted to have certificates issued using a given certificate
-profile.
-
-PROFILE ID SYNTAX:
-
-A Profile ID is a string without spaces or punctuation starting with a letter
-and followed by a sequence of letters, digits or underscore ("_").
-
-EXAMPLES:
-
- Create a CA ACL "test" that grants all users access to the
- "UserCert" profile:
- ipa caacl-add test --usercat=all
- ipa caacl-add-profile test --certprofiles UserCert
-
- Display the properties of a named CA ACL:
- ipa caacl-show test
-
- Create a CA ACL to let user "alice" use the "DNP3" profile:
- ipa caacl-add-profile alice_dnp3 --certprofiles DNP3
- ipa caacl-add-user alice_dnp3 --user=alice
-
- Disable a CA ACL:
- ipa caacl-disable test
-
- Remove a CA ACL:
- ipa caacl-del test
-""")
-
-register = Registry()
-
-
-def _acl_make_request(principal_type, principal, ca_ref, profile_id):
- """Construct HBAC request for the given principal, CA and profile"""
- service, name, realm = split_any_principal(principal)
-
- req = pyhbac.HbacRequest()
- req.targethost.name = ca_ref
- req.service.name = profile_id
- if principal_type == 'user':
- req.user.name = name
- elif principal_type == 'host':
- req.user.name = name
- elif principal_type == 'service':
- req.user.name = normalize_principal(principal)
- groups = []
- if principal_type == 'user':
- user_obj = api.Command.user_show(name)['result']
- groups = user_obj.get('memberof_group', [])
- groups += user_obj.get('memberofindirect_group', [])
- elif principal_type == 'host':
- host_obj = api.Command.host_show(name)['result']
- groups = host_obj.get('memberof_hostgroup', [])
- groups += host_obj.get('memberofindirect_hostgroup', [])
- req.user.groups = sorted(set(groups))
- return req
-
-
-def _acl_make_rule(principal_type, obj):
- """Turn CA ACL object into HBAC rule.
-
- ``principal_type``
- String in {'user', 'host', 'service'}
- """
- rule = pyhbac.HbacRule(obj['cn'][0])
- rule.enabled = obj['ipaenabledflag'][0]
- rule.srchosts.category = {pyhbac.HBAC_CATEGORY_ALL}
-
- # add CA(s)
- # Hardcoded until caacl plugin arrives
- rule.targethosts.category = {pyhbac.HBAC_CATEGORY_ALL}
- #if 'ipacacategory' in obj and obj['ipacacategory'][0].lower() == 'all':
- # rule.targethosts.category = {pyhbac.HBAC_CATEGORY_ALL}
- #else:
- # rule.targethosts.names = obj.get('ipacaaclcaref', [])
-
- # add profiles
- if ('ipacertprofilecategory' in obj
- and obj['ipacertprofilecategory'][0].lower() == 'all'):
- rule.services.category = {pyhbac.HBAC_CATEGORY_ALL}
- else:
- attr = 'ipamembercertprofile_certprofile'
- rule.services.names = obj.get(attr, [])
-
- # add principals and principal's groups
- m = {'user': 'group', 'host': 'hostgroup', 'service': None}
- category_attr = '{}category'.format(principal_type)
- if category_attr in obj and obj[category_attr][0].lower() == 'all':
- rule.users.category = {pyhbac.HBAC_CATEGORY_ALL}
- else:
- principal_attr = 'member{}_{}'.format(principal_type, principal_type)
- rule.users.names = obj.get(principal_attr, [])
- if m[principal_type] is not None:
- group_attr = 'member{}_{}'.format(principal_type, m[principal_type])
- rule.users.groups = obj.get(group_attr, [])
-
- return rule
-
-
-def acl_evaluate(principal_type, principal, ca_ref, profile_id):
- req = _acl_make_request(principal_type, principal, ca_ref, profile_id)
- acls = api.Command.caacl_find(no_members=False)['result']
- rules = [_acl_make_rule(principal_type, obj) for obj in acls]
- return req.evaluate(rules) == pyhbac.HBAC_EVAL_ALLOW
-
-
-@register()
-class caacl(LDAPObject):
- """
- CA ACL object.
- """
- container_dn = api.env.container_caacl
- object_name = _('CA ACL')
- object_name_plural = _('CA ACLs')
- object_class = ['ipaassociation', 'ipacaacl']
- permission_filter_objectclasses = ['ipacaacl']
- default_attributes = [
- 'cn', 'description', 'ipaenabledflag',
- 'ipacacategory', 'ipamemberca',
- 'ipacertprofilecategory', 'ipamembercertprofile',
- 'usercategory', 'memberuser',
- 'hostcategory', 'memberhost',
- 'servicecategory', 'memberservice',
- ]
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- attribute_members = {
- 'memberuser': ['user', 'group'],
- 'memberhost': ['host', 'hostgroup'],
- 'memberservice': ['service'],
- 'ipamembercertprofile': ['certprofile'],
- }
- managed_permissions = {
- 'System: Read CA ACLs': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'ipaenabledflag',
- 'ipacacategory', 'ipamemberca',
- 'ipacertprofilecategory', 'ipamembercertprofile',
- 'usercategory', 'memberuser',
- 'hostcategory', 'memberhost',
- 'servicecategory', 'memberservice',
- 'ipauniqueid',
- 'objectclass', 'member',
- },
- },
- 'System: Add CA ACL': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add CA ACL";allow (add) groupdn = "ldap:///cn=Add CA ACL,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- 'System: Delete CA ACL': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete CA ACL";allow (delete) groupdn = "ldap:///cn=Delete CA ACL,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- 'System: Manage CA ACL Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'ipacacategory', 'ipamemberca',
- 'ipacertprofilecategory', 'ipamembercertprofile',
- 'usercategory', 'memberuser',
- 'hostcategory', 'memberhost',
- 'servicecategory', 'memberservice'
- },
- 'replaces': [
- '(targetattr = "ipamemberca || ipamembercertprofile || memberuser || memberservice || memberhost || ipacacategory || ipacertprofilecategory || usercategory || hostcategory || servicecategory")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Manage CA ACL membership";allow (write) groupdn = "ldap:///cn=Manage CA ACL membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- 'System: Modify CA ACL': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'ipaenabledflag',
- },
- 'replaces': [
- '(targetattr = "cn || description || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify CA ACL";allow (write) groupdn = "ldap:///cn=Modify CA ACL,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- }
-
- label = _('CA ACLs')
- label_singular = _('CA ACL')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('ACL name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- Bool('ipaenabledflag?',
- label=_('Enabled'),
- flags=['no_option'],
- ),
- # Commented until subca plugin arrives
- #StrEnum('ipacacategory?',
- # cli_name='cacat',
- # label=_('CA category'),
- # doc=_('CA category the ACL applies to'),
- # values=(u'all', ),
- #),
- StrEnum('ipacertprofilecategory?',
- cli_name='profilecat',
- label=_('Profile category'),
- doc=_('Profile category the ACL applies to'),
- values=(u'all', ),
- ),
- StrEnum('usercategory?',
- cli_name='usercat',
- label=_('User category'),
- doc=_('User category the ACL applies to'),
- values=(u'all', ),
- ),
- StrEnum('hostcategory?',
- cli_name='hostcat',
- label=_('Host category'),
- doc=_('Host category the ACL applies to'),
- values=(u'all', ),
- ),
- StrEnum('servicecategory?',
- cli_name='servicecat',
- label=_('Service category'),
- doc=_('Service category the ACL applies to'),
- values=(u'all', ),
- ),
- # Commented until subca plugin arrives
- #Str('ipamemberca_subca?',
- # label=_('CAs'),
- # flags=['no_create', 'no_update', 'no_search'],
- #),
- Str('ipamembercertprofile_certprofile?',
- label=_('Profiles'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberuser_user?',
- label=_('Users'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberuser_group?',
- label=_('User Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_host?',
- label=_('Hosts'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_hostgroup?',
- label=_('Host Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberservice_service?',
- label=_('Services'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- )
-
-
-@register()
-class caacl_add(LDAPCreate):
- __doc__ = _('Create a new CA ACL.')
-
- msg_summary = _('Added CA ACL "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- # CA ACLs are enabled by default
- entry_attrs['ipaenabledflag'] = ['TRUE']
- return dn
-
-
-@register()
-class caacl_del(LDAPDelete):
- __doc__ = _('Delete a CA ACL.')
-
- msg_summary = _('Deleted CA ACL "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- if keys[0] == 'hosts_services_caIPAserviceCert':
- raise errors.ProtectedEntryError(
- label=_("CA ACL"),
- key=keys[0],
- reason=_("default CA ACL can be only disabled"))
- return dn
-
-
-@register()
-class caacl_mod(LDAPUpdate):
- __doc__ = _('Modify a CA ACL.')
-
- msg_summary = _('Modified CA ACL "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, attrs_list)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- # Commented until subca plugin arrives
- #if is_all(options, 'ipacacategory') and 'ipamemberca' in entry_attrs:
- # raise errors.MutuallyExclusiveError(reason=_(
- # "CA category cannot be set to 'all' "
- # "while there are allowed CAs"))
- if (is_all(options, 'ipacertprofilecategory')
- and 'ipamembercertprofile' in entry_attrs):
- raise errors.MutuallyExclusiveError(reason=_(
- "profile category cannot be set to 'all' "
- "while there are allowed profiles"))
- if is_all(options, 'usercategory') and 'memberuser' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_(
- "user category cannot be set to 'all' "
- "while there are allowed users"))
- if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_(
- "host category cannot be set to 'all' "
- "while there are allowed hosts"))
- if is_all(options, 'servicecategory') and 'memberservice' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_(
- "service category cannot be set to 'all' "
- "while there are allowed services"))
- return dn
-
-
-@register()
-class caacl_find(LDAPSearch):
- __doc__ = _('Search for CA ACLs.')
-
- msg_summary = ngettext(
- '%(count)d CA ACL matched', '%(count)d CA ACLs matched', 0
- )
-
-
-@register()
-class caacl_show(LDAPRetrieve):
- __doc__ = _('Display the properties of a CA ACL.')
-
-
-@register()
-class caacl_enable(LDAPQuery):
- __doc__ = _('Enable a CA ACL.')
-
- msg_summary = _('Enabled CA ACL "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['TRUE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-@register()
-class caacl_disable(LDAPQuery):
- __doc__ = _('Disable a CA ACL.')
-
- msg_summary = _('Disabled CA ACL "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['FALSE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-@register()
-class caacl_add_user(LDAPAddMember):
- __doc__ = _('Add users and groups to a CA ACL.')
-
- member_attributes = ['memberuser']
- member_count_out = (
- _('%i user or group added.'),
- _('%i users or groups added.'))
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(entry_attrs, 'usercategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when user category='all'"))
- return dn
-
-
-@register()
-class caacl_remove_user(LDAPRemoveMember):
- __doc__ = _('Remove users and groups from a CA ACL.')
-
- member_attributes = ['memberuser']
- member_count_out = (
- _('%i user or group removed.'),
- _('%i users or groups removed.'))
-
-
-@register()
-class caacl_add_host(LDAPAddMember):
- __doc__ = _('Add target hosts and hostgroups to a CA ACL.')
-
- member_attributes = ['memberhost']
- member_count_out = (
- _('%i host or hostgroup added.'),
- _('%i hosts or hostgroups added.'))
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(entry_attrs, 'hostcategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("hosts cannot be added when host category='all'"))
- return dn
-
-
-@register()
-class caacl_remove_host(LDAPRemoveMember):
- __doc__ = _('Remove target hosts and hostgroups from a CA ACL.')
-
- member_attributes = ['memberhost']
- member_count_out = (
- _('%i host or hostgroup removed.'),
- _('%i hosts or hostgroups removed.'))
-
-
-@register()
-class caacl_add_service(LDAPAddMember):
- __doc__ = _('Add services to a CA ACL.')
-
- member_attributes = ['memberservice']
- member_count_out = (_('%i service added.'), _('%i services added.'))
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(entry_attrs, 'servicecategory'):
- raise errors.MutuallyExclusiveError(reason=_(
- "services cannot be added when service category='all'"))
- return dn
-
-
-@register()
-class caacl_remove_service(LDAPRemoveMember):
- __doc__ = _('Remove services from a CA ACL.')
-
- member_attributes = ['memberservice']
- member_count_out = (_('%i service removed.'), _('%i services removed.'))
-
-
-caacl_output_params = global_output_params + (
- Str('ipamembercertprofile',
- label=_('Failed profiles'),
- ),
- # Commented until caacl plugin arrives
- #Str('ipamemberca',
- # label=_('Failed CAs'),
- #),
-)
-
-
-@register()
-class caacl_add_profile(LDAPAddMember):
- __doc__ = _('Add profiles to a CA ACL.')
-
- has_output_params = caacl_output_params
-
- member_attributes = ['ipamembercertprofile']
- member_count_out = (_('%i profile added.'), _('%i profiles added.'))
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(entry_attrs, 'ipacertprofilecategory'):
- raise errors.MutuallyExclusiveError(reason=_(
- "profiles cannot be added when profile category='all'"))
- return dn
-
-
-@register()
-class caacl_remove_profile(LDAPRemoveMember):
- __doc__ = _('Remove profiles from a CA ACL.')
-
- has_output_params = caacl_output_params
-
- member_attributes = ['ipamembercertprofile']
- member_count_out = (_('%i profile removed.'), _('%i profiles removed.'))
diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py
deleted file mode 100644
index cbb5382fb..000000000
--- a/ipalib/plugins/cert.py
+++ /dev/null
@@ -1,835 +0,0 @@
-# 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 os
-import time
-import binascii
-
-from ipalib import Command, Str, Int, Flag
-from ipalib import api
-from ipalib import errors
-from ipalib import pkcs10
-from ipalib import x509
-from ipalib import ngettext
-from ipalib.plugable import Registry
-from .virtual import VirtualCommand
-from .baseldap import pkey_to_value
-from .service import split_any_principal
-from .certprofile import validate_profile_id
-from .caacl import acl_evaluate
-from ipalib.text import _
-from ipalib.request import context
-from ipalib import output
-from .service import validate_principal
-from ipapython.dn import DN
-
-import six
-import nss.nss as nss
-from nss.error import NSPRError
-from pyasn1.error import PyAsn1Error
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-IPA certificate operations
-
-Implements a set of commands for managing server SSL certificates.
-
-Certificate requests exist in the form of a Certificate Signing Request (CSR)
-in PEM format.
-
-The dogtag CA uses just the CN value of the CSR and forces the rest of the
-subject to values configured in the server.
-
-A certificate is stored with a service principal and a service principal
-needs a host.
-
-In order to request a certificate:
-
-* The host must exist
-* The service must exist (or you use the --add option to automatically add it)
-
-SEARCHING:
-
-Certificates may be searched on by certificate subject, serial number,
-revocation reason, validity dates and the issued date.
-
-When searching on dates the _from date does a >= search and the _to date
-does a <= search. When combined these are done as an AND.
-
-Dates are treated as GMT to match the dates in the certificates.
-
-The date format is YYYY-mm-dd.
-
-EXAMPLES:
-
- Request a new certificate and add the principal:
- ipa cert-request --add --principal=HTTP/lion.example.com example.csr
-
- Retrieve an existing certificate:
- ipa cert-show 1032
-
- Revoke a certificate (see RFC 5280 for reason details):
- ipa cert-revoke --revocation-reason=6 1032
-
- Remove a certificate from revocation hold status:
- ipa cert-remove-hold 1032
-
- Check the status of a signing request:
- ipa cert-status 10
-
- Search for certificates by hostname:
- ipa cert-find --subject=ipaserver.example.com
-
- Search for revoked certificates by reason:
- ipa cert-find --revocation-reason=5
-
- Search for certificates based on issuance date
- ipa cert-find --issuedon-from=2013-02-01 --issuedon-to=2013-02-07
-
-IPA currently immediately issues (or declines) all certificate requests so
-the status of a request is not normally useful. This is for future use
-or the case where a CA does not immediately issue a certificate.
-
-The following revocation reasons are supported:
-
- * 0 - unspecified
- * 1 - keyCompromise
- * 2 - cACompromise
- * 3 - affiliationChanged
- * 4 - superseded
- * 5 - cessationOfOperation
- * 6 - certificateHold
- * 8 - removeFromCRL
- * 9 - privilegeWithdrawn
- * 10 - aACompromise
-
-Note that reason code 7 is not used. See RFC 5280 for more details:
-
-http://www.ietf.org/rfc/rfc5280.txt
-
-""")
-
-USER, HOST, SERVICE = range(3)
-
-register = Registry()
-
-def validate_pkidate(ugettext, value):
- """
- A date in the format of %Y-%m-%d
- """
- try:
- ts = time.strptime(value, '%Y-%m-%d')
- except ValueError as e:
- return str(e)
-
- return None
-
-def validate_csr(ugettext, csr):
- """
- Ensure the CSR is base64-encoded and can be decoded by our PKCS#10
- parser.
- """
- if api.env.context == 'cli':
- # If we are passed in a pointer to a valid file on the client side
- # escape and let the load_files() handle things
- if csr and os.path.exists(csr):
- return
- try:
- request = pkcs10.load_certificate_request(csr)
- except (TypeError, binascii.Error) as e:
- raise errors.Base64DecodeError(reason=str(e))
- except Exception as e:
- raise errors.CertificateOperationError(error=_('Failure decoding Certificate Signing Request: %s') % e)
-
-def normalize_csr(csr):
- """
- Strip any leading and trailing cruft around the BEGIN/END block
- """
- end_len = 37
- s = csr.find('-----BEGIN NEW CERTIFICATE REQUEST-----')
- if s == -1:
- s = csr.find('-----BEGIN CERTIFICATE REQUEST-----')
- e = csr.find('-----END NEW CERTIFICATE REQUEST-----')
- if e == -1:
- e = csr.find('-----END CERTIFICATE REQUEST-----')
- if e != -1:
- end_len = 33
-
- if s > -1 and e > -1:
- # We're normalizing here, not validating
- csr = csr[s:e+end_len]
-
- return csr
-
-def _convert_serial_number(num):
- """
- Convert a SN given in decimal or hexadecimal.
- Returns the number or None if conversion fails.
- """
- # plain decimal or hexa with radix prefix
- try:
- num = int(num, 0)
- except ValueError:
- try:
- # hexa without prefix
- num = int(num, 16)
- except ValueError:
- num = None
-
- return num
-
-def validate_serial_number(ugettext, num):
- if _convert_serial_number(num) == None:
- return u"Decimal or hexadecimal number is required for serial number"
- return None
-
-def normalize_serial_number(num):
- # It's been already validated
- return unicode(_convert_serial_number(num))
-
-def get_host_from_principal(principal):
- """
- Given a principal with or without a realm return the
- host portion.
- """
- validate_principal(None, principal)
- realm = principal.find('@')
- slash = principal.find('/')
- if realm == -1:
- realm = len(principal)
- hostname = principal[slash+1:realm]
-
- return hostname
-
-def ca_enabled_check():
- if not api.Command.ca_is_enabled()['result']:
- raise errors.NotFound(reason=_('CA is not configured'))
-
-def caacl_check(principal_type, principal_string, ca, profile_id):
- principal_type_map = {USER: 'user', HOST: 'host', SERVICE: 'service'}
- if not acl_evaluate(
- principal_type_map[principal_type],
- principal_string, ca, profile_id):
- raise errors.ACIError(info=_(
- "Principal '%(principal)s' "
- "is not permitted to use CA '%(ca)s' "
- "with profile '%(profile_id)s' for certificate issuance."
- ) % dict(
- principal=principal_string,
- ca=ca or '.',
- profile_id=profile_id
- )
- )
-
-@register()
-class cert_request(VirtualCommand):
- __doc__ = _('Submit a certificate signing request.')
-
- takes_args = (
- Str(
- 'csr', validate_csr,
- label=_('CSR'),
- cli_name='csr_file',
- normalizer=normalize_csr,
- noextrawhitespace=False,
- ),
- )
- operation="request certificate"
-
- takes_options = (
- Str('principal',
- label=_('Principal'),
- doc=_('Principal for this certificate (e.g. HTTP/test.example.com)'),
- ),
- Str('request_type',
- default=u'pkcs10',
- autofill=True,
- ),
- Flag('add',
- doc=_("automatically add the principal if it doesn't exist"),
- default=False,
- autofill=True
- ),
- Str('profile_id?', validate_profile_id,
- label=_("Profile ID"),
- doc=_("Certificate Profile to use"),
- )
- )
-
- has_output_params = (
- Str('certificate',
- label=_('Certificate'),
- ),
- Str('subject',
- label=_('Subject'),
- ),
- Str('issuer',
- label=_('Issuer'),
- ),
- Str('valid_not_before',
- label=_('Not Before'),
- ),
- Str('valid_not_after',
- label=_('Not After'),
- ),
- Str('md5_fingerprint',
- label=_('Fingerprint (MD5)'),
- ),
- Str('sha1_fingerprint',
- label=_('Fingerprint (SHA1)'),
- ),
- Str('serial_number',
- label=_('Serial number'),
- ),
- Str('serial_number_hex',
- label=_('Serial number (hex)'),
- ),
- )
-
- has_output = (
- output.Output('result',
- type=dict,
- doc=_('Dictionary mapping variable name to value'),
- ),
- )
-
- def execute(self, csr, **kw):
- ca_enabled_check()
-
- ldap = self.api.Backend.ldap2
- add = kw.get('add')
- request_type = kw.get('request_type')
- profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE)
- ca = '.' # top-level CA hardcoded until subca plugin implemented
-
- """
- Access control is partially handled by the ACI titled
- 'Hosts can modify service userCertificate'. This is for the case
- where a machine binds using a host/ prinicpal. It can only do the
- request if the target hostname is in the managedBy attribute which
- is managed using the add/del member commands.
-
- Binding with a user principal one needs to be in the request_certs
- taskgroup (directly or indirectly via role membership).
- """
-
- principal_string = kw.get('principal')
- principal = split_any_principal(principal_string)
- servicename, principal_name, realm = principal
- if servicename is None:
- principal_type = USER
- elif servicename == 'host':
- principal_type = HOST
- else:
- principal_type = SERVICE
-
- bind_principal = split_any_principal(getattr(context, 'principal'))
- bind_service, bind_name, bind_realm = bind_principal
-
- if bind_service is None:
- bind_principal_type = USER
- elif bind_service == 'host':
- bind_principal_type = HOST
- else:
- bind_principal_type = SERVICE
-
- if bind_principal != principal and bind_principal_type != HOST:
- # Can the bound principal request certs for another principal?
- self.check_access()
-
- try:
- self.check_access("request certificate ignore caacl")
- bypass_caacl = True
- except errors.ACIError:
- bypass_caacl = False
-
- if not bypass_caacl:
- caacl_check(principal_type, principal_string, ca, profile_id)
-
- try:
- subject = pkcs10.get_subject(csr)
- extensions = pkcs10.get_extensions(csr)
- subjectaltname = pkcs10.get_subjectaltname(csr) or ()
- except (NSPRError, PyAsn1Error, ValueError) as e:
- raise errors.CertificateOperationError(
- error=_("Failure decoding Certificate Signing Request: %s") % e)
-
- # self-service and host principals may bypass SAN permission check
- if bind_principal != principal and bind_principal_type != HOST:
- if '2.5.29.17' in extensions:
- self.check_access('request certificate with subjectaltname')
-
- dn = None
- principal_obj = None
- # See if the service exists and punt if it doesn't and we aren't
- # going to add it
- try:
- if principal_type == SERVICE:
- principal_obj = api.Command['service_show'](principal_string, all=True)
- elif principal_type == HOST:
- principal_obj = api.Command['host_show'](principal_name, all=True)
- elif principal_type == USER:
- principal_obj = api.Command['user_show'](principal_name, all=True)
- except errors.NotFound as e:
- if principal_type == SERVICE and add:
- principal_obj = api.Command['service_add'](principal_string, force=True)
- else:
- raise errors.NotFound(
- reason=_("The principal for this request doesn't exist."))
- principal_obj = principal_obj['result']
- dn = principal_obj['dn']
-
- # Ensure that the DN in the CSR matches the principal
- cn = subject.common_name #pylint: disable=E1101
- if not cn:
- raise errors.ValidationError(name='csr',
- error=_("No Common Name was found in subject of request."))
-
- if principal_type in (SERVICE, HOST):
- if cn.lower() != principal_name.lower():
- raise errors.ACIError(
- info=_("hostname in subject of request '%(cn)s' "
- "does not match principal hostname '%(hostname)s'")
- % dict(cn=cn, hostname=principal_name))
- elif principal_type == USER:
- # check user name
- if cn != principal_name:
- raise errors.ValidationError(
- name='csr',
- error=_("DN commonName does not match user's login")
- )
-
- # check email address
- mail = subject.email_address #pylint: disable=E1101
- if mail is not None and mail not in principal_obj.get('mail', []):
- raise errors.ValidationError(
- name='csr',
- error=_(
- "DN emailAddress does not match "
- "any of user's email addresses")
- )
-
- # We got this far so the principal entry exists, can we write it?
- if not ldap.can_write(dn, "usercertificate"):
- raise errors.ACIError(info=_("Insufficient 'write' privilege "
- "to the 'userCertificate' attribute of entry '%s'.") % dn)
-
- # Validate the subject alt name, if any
- for name_type, name in subjectaltname:
- if name_type == pkcs10.SAN_DNSNAME:
- name = unicode(name)
- alt_principal_obj = None
- alt_principal_string = None
- try:
- if principal_type == HOST:
- alt_principal_string = 'host/%s@%s' % (name, realm)
- alt_principal_obj = api.Command['host_show'](name, all=True)
- elif principal_type == SERVICE:
- alt_principal_string = '%s/%s@%s' % (servicename, name, realm)
- alt_principal_obj = api.Command['service_show'](
- alt_principal_string, all=True)
- elif principal_type == USER:
- raise errors.ValidationError(
- name='csr',
- error=_("subject alt name type %s is forbidden "
- "for user principals") % name_type
- )
- except errors.NotFound:
- # We don't want to issue any certificates referencing
- # machines we don't know about. Nothing is stored in this
- # host record related to this certificate.
- raise errors.NotFound(reason=_('The service principal for '
- 'subject alt name %s in certificate request does not '
- 'exist') % name)
- if alt_principal_obj is not None:
- altdn = alt_principal_obj['result']['dn']
- if not ldap.can_write(altdn, "usercertificate"):
- raise errors.ACIError(info=_(
- "Insufficient privilege to create a certificate "
- "with subject alt name '%s'.") % name)
- if alt_principal_string is not None and not bypass_caacl:
- caacl_check(
- principal_type, alt_principal_string, ca, profile_id)
- elif name_type in (pkcs10.SAN_OTHERNAME_KRB5PRINCIPALNAME,
- pkcs10.SAN_OTHERNAME_UPN):
- if split_any_principal(name) != principal:
- raise errors.ACIError(
- info=_("Principal '%s' in subject alt name does not "
- "match requested principal") % name)
- elif name_type == pkcs10.SAN_RFC822NAME:
- if principal_type == USER:
- if name not in principal_obj.get('mail', []):
- raise errors.ValidationError(
- name='csr',
- error=_(
- "RFC822Name does not match "
- "any of user's email addresses")
- )
- else:
- raise errors.ValidationError(
- name='csr',
- error=_("subject alt name type %s is forbidden "
- "for non-user principals") % name_type
- )
- else:
- raise errors.ACIError(
- info=_("Subject alt name type %s is forbidden") %
- name_type)
-
- # Request the certificate
- result = self.Backend.ra.request_certificate(
- csr, profile_id, request_type=request_type)
- cert = x509.load_certificate(result['certificate'])
- result['issuer'] = unicode(cert.issuer)
- result['valid_not_before'] = unicode(cert.valid_not_before_str)
- result['valid_not_after'] = unicode(cert.valid_not_after_str)
- result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
- result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
-
- # Success? Then add it to the principal's entry
- # (unless the profile tells us not to)
- profile = api.Command['certprofile_show'](profile_id)
- store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE'
- if store and 'certificate' in result:
- cert = str(result.get('certificate'))
- kwargs = dict(addattr=u'usercertificate={}'.format(cert))
- if principal_type == SERVICE:
- api.Command['service_mod'](principal_string, **kwargs)
- elif principal_type == HOST:
- api.Command['host_mod'](principal_name, **kwargs)
- elif principal_type == USER:
- api.Command['user_mod'](principal_name, **kwargs)
-
- return dict(
- result=result
- )
-
-
-
-@register()
-class cert_status(VirtualCommand):
- __doc__ = _('Check the status of a certificate signing request.')
-
- takes_args = (
- Str('request_id',
- label=_('Request id'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- )
- has_output_params = (
- Str('cert_request_status',
- label=_('Request status'),
- ),
- )
- operation = "certificate status"
-
-
- def execute(self, request_id, **kw):
- ca_enabled_check()
- self.check_access()
- return dict(
- result=self.Backend.ra.check_request_status(request_id)
- )
-
-
-
-_serial_number = Str('serial_number',
- validate_serial_number,
- label=_('Serial number'),
- doc=_('Serial number in decimal or if prefixed with 0x in hexadecimal'),
- normalizer=normalize_serial_number,
-)
-
-@register()
-class cert_show(VirtualCommand):
- __doc__ = _('Retrieve an existing certificate.')
-
- takes_args = _serial_number
-
- has_output_params = (
- Str('certificate',
- label=_('Certificate'),
- ),
- Str('subject',
- label=_('Subject'),
- ),
- Str('issuer',
- label=_('Issuer'),
- ),
- Str('valid_not_before',
- label=_('Not Before'),
- ),
- Str('valid_not_after',
- label=_('Not After'),
- ),
- Str('md5_fingerprint',
- label=_('Fingerprint (MD5)'),
- ),
- Str('sha1_fingerprint',
- label=_('Fingerprint (SHA1)'),
- ),
- Str('revocation_reason',
- label=_('Revocation reason'),
- ),
- Str('serial_number_hex',
- label=_('Serial number (hex)'),
- ),
- )
-
- takes_options = (
- Str('out?',
- label=_('Output filename'),
- doc=_('File to store the certificate in.'),
- exclude='webui',
- ),
- )
-
- operation="retrieve certificate"
-
- def execute(self, serial_number, **options):
- ca_enabled_check()
- hostname = None
- try:
- self.check_access()
- except errors.ACIError as acierr:
- self.debug("Not granted by ACI to retrieve certificate, looking at principal")
- bind_principal = getattr(context, 'principal')
- if not bind_principal.startswith('host/'):
- raise acierr
- hostname = get_host_from_principal(bind_principal)
-
- result=self.Backend.ra.get_certificate(serial_number)
- cert = x509.load_certificate(result['certificate'])
- result['subject'] = unicode(cert.subject)
- result['issuer'] = unicode(cert.issuer)
- result['valid_not_before'] = unicode(cert.valid_not_before_str)
- result['valid_not_after'] = unicode(cert.valid_not_after_str)
- result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
- result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
- if hostname:
- # If we have a hostname we want to verify that the subject
- # of the certificate matches it, otherwise raise an error
- if hostname != cert.subject.common_name: #pylint: disable=E1101
- raise acierr
-
- return dict(result=result)
-
-
-
-
-@register()
-class cert_revoke(VirtualCommand):
- __doc__ = _('Revoke a certificate.')
-
- takes_args = _serial_number
-
- has_output_params = (
- Flag('revoked',
- label=_('Revoked'),
- ),
- )
- operation = "revoke certificate"
-
- # FIXME: The default is 0. Is this really an Int param?
- takes_options = (
- Int('revocation_reason',
- label=_('Reason'),
- doc=_('Reason for revoking the certificate (0-10). Type '
- '"ipa help cert" for revocation reason details. '),
- minvalue=0,
- maxvalue=10,
- default=0,
- autofill=True
- ),
- )
-
- def execute(self, serial_number, **kw):
- ca_enabled_check()
- hostname = None
- try:
- self.check_access()
- except errors.ACIError as acierr:
- self.debug("Not granted by ACI to revoke certificate, looking at principal")
- try:
- # Let cert_show() handle verifying that the subject of the
- # cert we're dealing with matches the hostname in the principal
- result = api.Command['cert_show'](unicode(serial_number))['result']
- except errors.NotImplementedError:
- pass
- revocation_reason = kw['revocation_reason']
- if revocation_reason == 7:
- raise errors.CertificateOperationError(error=_('7 is not a valid revocation reason'))
- return dict(
- result=self.Backend.ra.revoke_certificate(
- serial_number, revocation_reason=revocation_reason)
- )
-
-
-
-@register()
-class cert_remove_hold(VirtualCommand):
- __doc__ = _('Take a revoked certificate off hold.')
-
- takes_args = _serial_number
-
- has_output_params = (
- Flag('unrevoked',
- label=_('Unrevoked'),
- ),
- Str('error_string',
- label=_('Error'),
- ),
- )
- operation = "certificate remove hold"
-
- def execute(self, serial_number, **kw):
- ca_enabled_check()
- self.check_access()
- return dict(
- result=self.Backend.ra.take_certificate_off_hold(serial_number)
- )
-
-
-
-@register()
-class cert_find(Command):
- __doc__ = _('Search for existing certificates.')
-
- takes_options = (
- Str('subject?',
- label=_('Subject'),
- doc=_('Subject'),
- autofill=False,
- ),
- Int('revocation_reason?',
- label=_('Reason'),
- doc=_('Reason for revoking the certificate (0-10). Type '
- '"ipa help cert" for revocation reason details.'),
- minvalue=0,
- maxvalue=10,
- autofill=False,
- ),
- Int('min_serial_number?',
- doc=_("minimum serial number"),
- autofill=False,
- minvalue=0,
- maxvalue=2147483647,
- ),
- Int('max_serial_number?',
- doc=_("maximum serial number"),
- autofill=False,
- minvalue=0,
- maxvalue=2147483647,
- ),
- Flag('exactly?',
- doc=_('match the common name exactly'),
- autofill=False,
- ),
- Str('validnotafter_from?', validate_pkidate,
- doc=_('Valid not after from this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('validnotafter_to?', validate_pkidate,
- doc=_('Valid not after to this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('validnotbefore_from?', validate_pkidate,
- doc=_('Valid not before from this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('validnotbefore_to?', validate_pkidate,
- doc=_('Valid not before to this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('issuedon_from?', validate_pkidate,
- doc=_('Issued on from this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('issuedon_to?', validate_pkidate,
- doc=_('Issued on to this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('revokedon_from?', validate_pkidate,
- doc=_('Revoked on from this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Str('revokedon_to?', validate_pkidate,
- doc=_('Revoked on to this date (YYYY-mm-dd)'),
- autofill=False,
- ),
- Int('sizelimit?',
- label=_('Size Limit'),
- doc=_('Maximum number of certs returned'),
- flags=['no_display'],
- minvalue=0,
- default=100,
- ),
- )
-
- has_output = output.standard_list_of_entries
- has_output_params = (
- Str('serial_number_hex',
- label=_('Serial number (hex)'),
- ),
- Str('serial_number',
- label=_('Serial number'),
- ),
- Str('status',
- label=_('Status'),
- ),
- )
-
- msg_summary = ngettext(
- '%(count)d certificate matched', '%(count)d certificates matched', 0
- )
-
- def execute(self, **options):
- ca_enabled_check()
- ret = dict(
- result=self.Backend.ra.find(options)
- )
- ret['count'] = len(ret['result'])
- ret['truncated'] = False
- return ret
-
-
-@register()
-class ca_is_enabled(Command):
- """
- Checks if any of the servers has the CA service enabled.
- """
- NO_CLI = True
- has_output = output.standard_value
-
- def execute(self, *args, **options):
- base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
- self.api.env.basedn)
- filter = '(&(objectClass=ipaConfigObject)(cn=CA))'
- try:
- self.api.Backend.ldap2.find_entries(
- base_dn=base_dn, filter=filter, attrs_list=[])
- except errors.NotFound:
- result = False
- else:
- result = True
- return dict(result=result, value=pkey_to_value(None, options))
diff --git a/ipalib/plugins/certprofile.py b/ipalib/plugins/certprofile.py
deleted file mode 100644
index 6f314e1a4..000000000
--- a/ipalib/plugins/certprofile.py
+++ /dev/null
@@ -1,335 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-import re
-
-from ipalib import api, Bool, Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject, LDAPSearch, LDAPCreate,
- LDAPDelete, LDAPUpdate, LDAPRetrieve)
-from ipalib.request import context
-from ipalib import ngettext
-from ipalib.text import _
-from ipapython.dogtag import INCLUDED_PROFILES
-from ipapython.version import API_VERSION
-
-from ipalib import errors
-
-
-__doc__ = _("""
-Manage Certificate Profiles
-
-Certificate Profiles are used by Certificate Authority (CA) in the signing of
-certificates to determine if a Certificate Signing Request (CSR) is acceptable,
-and if so what features and extensions will be present on the certificate.
-
-The Certificate Profile format is the property-list format understood by the
-Dogtag or Red Hat Certificate System CA.
-
-PROFILE ID SYNTAX:
-
-A Profile ID is a string without spaces or punctuation starting with a letter
-and followed by a sequence of letters, digits or underscore ("_").
-
-EXAMPLES:
-
- Import a profile that will not store issued certificates:
- ipa certprofile-import ShortLivedUserCert \\
- --file UserCert.profile --desc "User Certificates" \\
- --store=false
-
- Delete a certificate profile:
- ipa certprofile-del ShortLivedUserCert
-
- Show information about a profile:
- ipa certprofile-show ShortLivedUserCert
-
- Save profile configuration to a file:
- ipa certprofile-show caIPAserviceCert --out caIPAserviceCert.cfg
-
- Search for profiles that do not store certificates:
- ipa certprofile-find --store=false
-
-PROFILE CONFIGURATION FORMAT:
-
-The profile configuration format is the raw property-list format
-used by Dogtag Certificate System. The XML format is not supported.
-
-The following restrictions apply to profiles managed by FreeIPA:
-
-- When importing a profile the "profileId" field, if present, must
- match the ID given on the command line.
-
-- The "classId" field must be set to "caEnrollImpl"
-
-- The "auth.instance_id" field must be set to "raCertAuth"
-
-- The "certReqInputImpl" input class and "certOutputImpl" output
- class must be used.
-
-""")
-
-
-register = Registry()
-
-
-def ca_enabled_check():
- """Raise NotFound if CA is not enabled.
-
- This function is defined in multiple plugins to avoid circular imports
- (cert depends on certprofile, so we cannot import cert here).
-
- """
- if not api.Command.ca_is_enabled()['result']:
- raise errors.NotFound(reason=_('CA is not configured'))
-
-
-profile_id_pattern = re.compile('^[a-zA-Z]\w*$')
-
-
-def validate_profile_id(ugettext, value):
- """Ensure profile ID matches form required by CA."""
- if profile_id_pattern.match(value) is None:
- return _('invalid Profile ID')
- else:
- return None
-
-
-@register()
-class certprofile(LDAPObject):
- """
- Certificate Profile object.
- """
- container_dn = api.env.container_certprofile
- object_name = _('Certificate Profile')
- object_name_plural = _('Certificate Profiles')
- object_class = ['ipacertprofile']
- default_attributes = [
- 'cn', 'description', 'ipacertprofilestoreissued'
- ]
- search_attributes = [
- 'cn', 'description', 'ipacertprofilestoreissued'
- ]
- label = _('Certificate Profiles')
- label_singular = _('Certificate Profile')
-
- takes_params = (
- Str('cn', validate_profile_id,
- primary_key=True,
- cli_name='id',
- label=_('Profile ID'),
- doc=_('Profile ID for referring to this profile'),
- ),
- Str('description',
- required=True,
- cli_name='desc',
- label=_('Profile description'),
- doc=_('Brief description of this profile'),
- ),
- Bool('ipacertprofilestoreissued',
- default=True,
- cli_name='store',
- label=_('Store issued certificates'),
- doc=_('Whether to store certs issued using this profile'),
- ),
- )
-
- permission_filter_objectclasses = ['ipacertprofile']
- managed_permissions = {
- 'System: Read Certificate Profiles': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn',
- 'description',
- 'ipacertprofilestoreissued',
- 'objectclass',
- },
- },
- 'System: Import Certificate Profile': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Import Certificate Profile";allow (add) groupdn = "ldap:///cn=Import Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- 'System: Delete Certificate Profile': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=Delete Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- 'System: Modify Certificate Profile': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'cn',
- 'description',
- 'ipacertprofilestoreissued',
- },
- 'replaces': [
- '(targetattr = "cn || description || ipacertprofilestoreissued")(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify Certificate Profile";allow (write) groupdn = "ldap:///cn=Modify Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'CA Administrator'},
- },
- }
-
-
-
-@register()
-class certprofile_find(LDAPSearch):
- __doc__ = _("Search for Certificate Profiles.")
- msg_summary = ngettext(
- '%(count)d profile matched', '%(count)d profiles matched', 0
- )
-
- def execute(self, *args, **kwargs):
- ca_enabled_check()
- return super(certprofile_find, self).execute(*args, **kwargs)
-
-
-@register()
-class certprofile_show(LDAPRetrieve):
- __doc__ = _("Display the properties of a Certificate Profile.")
-
- has_output_params = LDAPRetrieve.has_output_params + (
- Str('config',
- label=_('Profile configuration'),
- ),
- )
-
- takes_options = LDAPRetrieve.takes_options + (
- Str('out?',
- doc=_('Write profile configuration to file'),
- ),
- )
-
- def execute(self, *keys, **options):
- ca_enabled_check()
- result = super(certprofile_show, self).execute(*keys, **options)
-
- if 'out' in options:
- with self.api.Backend.ra_certprofile as profile_api:
- result['result']['config'] = profile_api.read_profile(keys[0])
-
- return result
-
-
-@register()
-class certprofile_import(LDAPCreate):
- __doc__ = _("Import a Certificate Profile.")
- msg_summary = _('Imported profile "%(value)s"')
- takes_options = (
- Str(
- 'file',
- label=_('Filename of a raw profile. The XML format is not supported.'),
- cli_name='file',
- flags=('virtual_attribute',),
- noextrawhitespace=False,
- ),
- )
-
- PROFILE_ID_PATTERN = re.compile('^profileId=([a-zA-Z]\w*)', re.MULTILINE)
-
- def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
- ca_enabled_check()
- context.profile = options['file']
-
- match = self.PROFILE_ID_PATTERN.search(options['file'])
- if match is None:
- # no profileId found, use CLI value as profileId.
- context.profile = u'profileId=%s\n%s' % (keys[0], context.profile)
- elif keys[0] != match.group(1):
- raise errors.ValidationError(name='file',
- error=_("Profile ID '%(cli_value)s' does not match profile data '%(file_value)s'")
- % {'cli_value': keys[0], 'file_value': match.group(1)}
- )
- return dn
-
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- """Import the profile into Dogtag and enable it.
-
- If the operation fails, remove the LDAP entry.
- """
- try:
- with self.api.Backend.ra_certprofile as profile_api:
- profile_api.create_profile(context.profile)
- profile_api.enable_profile(keys[0])
- except:
- # something went wrong ; delete entry
- ldap.delete_entry(dn)
- raise
-
- return dn
-
-
-@register()
-class certprofile_del(LDAPDelete):
- __doc__ = _("Delete a Certificate Profile.")
- msg_summary = _('Deleted profile "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- ca_enabled_check()
-
- if keys[0] in [p.profile_id for p in INCLUDED_PROFILES]:
- raise errors.ValidationError(name='profile_id',
- error=_("Predefined profile '%(profile_id)s' cannot be deleted")
- % {'profile_id': keys[0]}
- )
-
- return dn
-
- def post_callback(self, ldap, dn, *keys, **options):
- with self.api.Backend.ra_certprofile as profile_api:
- profile_api.disable_profile(keys[0])
- profile_api.delete_profile(keys[0])
- return dn
-
-
-@register()
-class certprofile_mod(LDAPUpdate):
- __doc__ = _("Modify Certificate Profile configuration.")
- msg_summary = _('Modified Certificate Profile "%(value)s"')
-
- takes_options = LDAPUpdate.takes_options + (
- Str(
- 'file?',
- label=_('File containing profile configuration'),
- cli_name='file',
- flags=('virtual_attribute',),
- noextrawhitespace=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- ca_enabled_check()
- # Once a profile id is set it cannot be changed
- if 'cn' in entry_attrs:
- raise errors.ProtectedEntryError(label='certprofile', key=keys[0],
- reason=_('Certificate profiles cannot be renamed'))
- if 'file' in options:
- with self.api.Backend.ra_certprofile as profile_api:
- profile_api.disable_profile(keys[0])
- try:
- profile_api.update_profile(keys[0], options['file'])
- finally:
- profile_api.enable_profile(keys[0])
-
- return dn
-
- def execute(self, *keys, **options):
- try:
- return super(certprofile_mod, self).execute(*keys, **options)
- except errors.EmptyModlist:
- if 'file' in options:
- # The profile data in Dogtag was updated.
- # Do not fail; return result of certprofile-show instead
- return self.api.Command.certprofile_show(keys[0],
- version=API_VERSION)
- else:
- # This case is actually an error; re-raise
- raise
diff --git a/ipalib/plugins/config.py b/ipalib/plugins/config.py
deleted file mode 100644
index 46a40ddf7..000000000
--- a/ipalib/plugins/config.py
+++ /dev/null
@@ -1,358 +0,0 @@
-# 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 ipalib import api
-from ipalib import Bool, Int, Str, IA5Str, StrEnum, DNParam
-from ipalib import errors
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPUpdate,
- LDAPRetrieve)
-from .selinuxusermap import validate_selinuxuser
-from ipalib import _
-from ipapython.dn import DN
-
-# 389-ds attributes that should be skipped in attribute checks
-OPERATIONAL_ATTRIBUTES = ('nsaccountlock', 'member', 'memberof',
- 'memberindirect', 'memberofindirect',)
-
-__doc__ = _("""
-Server configuration
-
-Manage the default values that IPA uses and some of its tuning parameters.
-
-NOTES:
-
-The password notification value (--pwdexpnotify) is stored here so it will
-be replicated. It is not currently used to notify users in advance of an
-expiring password.
-
-Some attributes are read-only, provided only for information purposes. These
-include:
-
-Certificate Subject base: the configured certificate subject base,
- e.g. O=EXAMPLE.COM. This is configurable only at install time.
-Password plug-in features: currently defines additional hashes that the
- password will generate (there may be other conditions).
-
-When setting the order list for mapping SELinux users you may need to
-quote the value so it isn't interpreted by the shell.
-
-EXAMPLES:
-
- Show basic server configuration:
- ipa config-show
-
- Show all configuration options:
- ipa config-show --all
-
- Change maximum username length to 99 characters:
- ipa config-mod --maxusername=99
-
- Increase default time and size limits for maximum IPA server search:
- ipa config-mod --searchtimelimit=10 --searchrecordslimit=2000
-
- Set default user e-mail domain:
- ipa config-mod --emaildomain=example.com
-
- Enable migration mode to make "ipa migrate-ds" command operational:
- ipa config-mod --enable-migration=TRUE
-
- Define SELinux user map order:
- ipa config-mod --ipaselinuxusermaporder='guest_u:s0$xguest_u:s0$user_u:s0-s0:c0.c1023$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023'
-""")
-
-register = Registry()
-
-@register()
-class config(LDAPObject):
- """
- IPA configuration object
- """
- object_name = _('configuration options')
- default_attributes = [
- 'ipamaxusernamelength', 'ipahomesrootdir', 'ipadefaultloginshell',
- 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit',
- 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields',
- 'ipamigrationenabled', 'ipacertificatesubjectbase',
- 'ipapwdexpadvnotify', 'ipaselinuxusermaporder',
- 'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata',
- 'ipauserauthtype'
- ]
- container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc'))
- permission_filter_objectclasses = ['ipaguiconfig']
- managed_permissions = {
- 'System: Read Global Configuration': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass',
- 'ipacertificatesubjectbase', 'ipaconfigstring',
- 'ipadefaultemaildomain', 'ipadefaultloginshell',
- 'ipadefaultprimarygroup', 'ipagroupobjectclasses',
- 'ipagroupsearchfields', 'ipahomesrootdir',
- 'ipakrbauthzdata', 'ipamaxusernamelength',
- 'ipamigrationenabled', 'ipapwdexpadvnotify',
- 'ipaselinuxusermapdefault', 'ipaselinuxusermaporder',
- 'ipasearchrecordslimit', 'ipasearchtimelimit',
- 'ipauserauthtype', 'ipauserobjectclasses',
- 'ipausersearchfields', 'ipacustomfields',
- },
- },
- }
-
- label = _('Configuration')
- label_singular = _('Configuration')
-
- takes_params = (
- Int('ipamaxusernamelength',
- cli_name='maxusername',
- label=_('Maximum username length'),
- minvalue=1,
- maxvalue=255,
- ),
- IA5Str('ipahomesrootdir',
- cli_name='homedirectory',
- label=_('Home directory base'),
- doc=_('Default location of home directories'),
- ),
- Str('ipadefaultloginshell',
- cli_name='defaultshell',
- label=_('Default shell'),
- doc=_('Default shell for new users'),
- ),
- Str('ipadefaultprimarygroup',
- cli_name='defaultgroup',
- label=_('Default users group'),
- doc=_('Default group for new users'),
- ),
- Str('ipadefaultemaildomain?',
- cli_name='emaildomain',
- label=_('Default e-mail domain'),
- doc=_('Default e-mail domain'),
- ),
- Int('ipasearchtimelimit',
- cli_name='searchtimelimit',
- label=_('Search time limit'),
- doc=_('Maximum amount of time (seconds) for a search (-1 or 0 is unlimited)'),
- minvalue=-1,
- ),
- Int('ipasearchrecordslimit',
- cli_name='searchrecordslimit',
- label=_('Search size limit'),
- doc=_('Maximum number of records to search (-1 or 0 is unlimited)'),
- minvalue=-1,
- ),
- IA5Str('ipausersearchfields',
- cli_name='usersearch',
- label=_('User search fields'),
- doc=_('A comma-separated list of fields to search in when searching for users'),
- ),
- IA5Str('ipagroupsearchfields',
- cli_name='groupsearch',
- label='Group search fields',
- doc=_('A comma-separated list of fields to search in when searching for groups'),
- ),
- Bool('ipamigrationenabled',
- cli_name='enable_migration',
- label=_('Enable migration mode'),
- doc=_('Enable migration mode'),
- ),
- DNParam('ipacertificatesubjectbase',
- cli_name='subject',
- label=_('Certificate Subject base'),
- doc=_('Base for certificate subjects (OU=Test,O=Example)'),
- flags=['no_update'],
- ),
- Str('ipagroupobjectclasses+',
- cli_name='groupobjectclasses',
- label=_('Default group objectclasses'),
- doc=_('Default group objectclasses (comma-separated list)'),
- ),
- Str('ipauserobjectclasses+',
- cli_name='userobjectclasses',
- label=_('Default user objectclasses'),
- doc=_('Default user objectclasses (comma-separated list)'),
- ),
- Int('ipapwdexpadvnotify',
- cli_name='pwdexpnotify',
- label=_('Password Expiration Notification (days)'),
- doc=_('Number of days\'s notice of impending password expiration'),
- minvalue=0,
- ),
- StrEnum('ipaconfigstring*',
- cli_name='ipaconfigstring',
- label=_('Password plugin features'),
- doc=_('Extra hashes to generate in password plug-in'),
- values=(u'AllowNThash',
- u'KDC:Disable Last Success', u'KDC:Disable Lockout',
- u'KDC:Disable Default Preauth for SPNs'),
- ),
- Str('ipaselinuxusermaporder',
- label=_('SELinux user map order'),
- doc=_('Order in increasing priority of SELinux users, delimited by $'),
- ),
- Str('ipaselinuxusermapdefault?',
- label=_('Default SELinux user'),
- doc=_('Default SELinux user when no match is found in SELinux map rule'),
- ),
- StrEnum('ipakrbauthzdata*',
- cli_name='pac_type',
- label=_('Default PAC types'),
- doc=_('Default types of PAC supported for services'),
- values=(u'MS-PAC', u'PAD', u'nfs:NONE'),
- ),
- StrEnum('ipauserauthtype*',
- cli_name='user_auth_type',
- label=_('Default user authentication types'),
- doc=_('Default types of supported user authentication'),
- values=(u'password', u'radius', u'otp', u'disabled'),
- ),
- )
-
- def get_dn(self, *keys, **kwargs):
- return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn)
-
-
-
-@register()
-class config_mod(LDAPUpdate):
- __doc__ = _('Modify configuration options.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- if 'ipadefaultprimarygroup' in entry_attrs:
- group=entry_attrs['ipadefaultprimarygroup']
- try:
- api.Object['group'].get_dn_if_exists(group)
- except errors.NotFound:
- raise errors.NotFound(message=_("The group doesn't exist"))
- kw = {}
- if 'ipausersearchfields' in entry_attrs:
- kw['ipausersearchfields'] = 'ipauserobjectclasses'
- if 'ipagroupsearchfields' in entry_attrs:
- kw['ipagroupsearchfields'] = 'ipagroupobjectclasses'
- if kw:
- config = ldap.get_ipa_config(list(kw.values()))
- for (k, v) in kw.items():
- allowed_attrs = ldap.get_allowed_attributes(config[v])
- fields = entry_attrs[k].split(',')
- for a in fields:
- a = a.strip()
- a, tomato, olive = a.partition(';')
- if a not in allowed_attrs:
- raise errors.ValidationError(
- name=k, error=_('attribute "%s" not allowed') % a
- )
-
- # Set ipasearchrecordslimit to -1 if 0 is used
- if 'ipasearchrecordslimit' in entry_attrs:
- if entry_attrs['ipasearchrecordslimit'] is 0:
- entry_attrs['ipasearchrecordslimit'] = -1
-
- # Set ipasearchtimelimit to -1 if 0 is used
- if 'ipasearchtimelimit' in entry_attrs:
- if entry_attrs['ipasearchtimelimit'] is 0:
- entry_attrs['ipasearchtimelimit'] = -1
-
- for (attr, obj) in (('ipauserobjectclasses', 'user'),
- ('ipagroupobjectclasses', 'group')):
- if attr in entry_attrs:
- if not entry_attrs[attr]:
- raise errors.ValidationError(name=attr,
- error=_('May not be empty'))
- objectclasses = list(set(entry_attrs[attr]).union(
- self.api.Object[obj].possible_objectclasses))
- new_allowed_attrs = ldap.get_allowed_attributes(objectclasses,
- raise_on_unknown=True)
- checked_attrs = self.api.Object[obj].default_attributes
- if self.api.Object[obj].uuid_attribute:
- checked_attrs = checked_attrs + [self.api.Object[obj].uuid_attribute]
- for obj_attr in checked_attrs:
- obj_attr, tomato, olive = obj_attr.partition(';')
- if obj_attr in OPERATIONAL_ATTRIBUTES:
- continue
- if obj_attr in self.api.Object[obj].params and \
- 'virtual_attribute' in \
- self.api.Object[obj].params[obj_attr].flags:
- # skip virtual attributes
- continue
- if obj_attr not in new_allowed_attrs:
- raise errors.ValidationError(name=attr,
- error=_('%(obj)s default attribute %(attr)s would not be allowed!') \
- % dict(obj=obj, attr=obj_attr))
-
- if ('ipaselinuxusermapdefault' in entry_attrs or
- 'ipaselinuxusermaporder' in entry_attrs):
- config = None
- failedattr = 'ipaselinuxusermaporder'
-
- if 'ipaselinuxusermapdefault' in entry_attrs:
- defaultuser = entry_attrs['ipaselinuxusermapdefault']
- failedattr = 'ipaselinuxusermapdefault'
-
- # validate the new default user first
- if defaultuser is not None:
- error_message = validate_selinuxuser(_, defaultuser)
-
- if error_message:
- raise errors.ValidationError(name='ipaselinuxusermapdefault',
- error=error_message)
-
- else:
- config = ldap.get_ipa_config()
- defaultuser = config.get('ipaselinuxusermapdefault', [None])[0]
-
- if 'ipaselinuxusermaporder' in entry_attrs:
- order = entry_attrs['ipaselinuxusermaporder']
- userlist = order.split('$')
-
- # validate the new user order first
- for user in userlist:
- if not user:
- raise errors.ValidationError(name='ipaselinuxusermaporder',
- error=_('A list of SELinux users delimited by $ expected'))
-
- error_message = validate_selinuxuser(_, user)
- if error_message:
- error_message = _("SELinux user '%(user)s' is not "
- "valid: %(error)s") % dict(user=user,
- error=error_message)
- raise errors.ValidationError(name='ipaselinuxusermaporder',
- error=error_message)
- else:
- if not config:
- config = ldap.get_ipa_config()
- order = config['ipaselinuxusermaporder']
- userlist = order[0].split('$')
- if defaultuser and defaultuser not in userlist:
- raise errors.ValidationError(name=failedattr,
- error=_('SELinux user map default user not in order list'))
-
- return dn
-
-
-
-@register()
-class config_show(LDAPRetrieve):
- __doc__ = _('Show the current configuration.')
-
diff --git a/ipalib/plugins/delegation.py b/ipalib/plugins/delegation.py
deleted file mode 100644
index 0443f0e48..000000000
--- a/ipalib/plugins/delegation.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# Martin Kosek <mkosek@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 ipalib import _, ngettext
-from ipalib import Str
-from ipalib import api, crud
-from ipalib import output
-from ipalib import Object
-from ipalib.plugable import Registry
-from .baseldap import gen_pkey_only_option, pkey_to_value
-
-__doc__ = _("""
-Group to Group Delegation
-
-A permission enables fine-grained delegation of permissions. Access Control
-Rules, or instructions (ACIs), grant permission to permissions to perform
-given tasks such as adding a user, modifying a group, etc.
-
-Group to Group Delegations grants the members of one group to update a set
-of attributes of members of another group.
-
-EXAMPLES:
-
- Add a delegation rule to allow managers to edit employee's addresses:
- ipa delegation-add --attrs=street --group=managers --membergroup=employees "managers edit employees' street"
-
- When managing the list of attributes you need to include all attributes
- in the list, including existing ones. Add postalCode to the list:
- ipa delegation-mod --attrs=street --attrs=postalCode --group=managers --membergroup=employees "managers edit employees' street"
-
- Display our updated rule:
- ipa delegation-show "managers edit employees' street"
-
- Delete a rule:
- ipa delegation-del "managers edit employees' street"
-""")
-
-register = Registry()
-
-ACI_PREFIX=u"delegation"
-
-output_params = (
- Str('aci',
- label=_('ACI'),
- ),
-)
-
-@register()
-class delegation(Object):
- """
- Delegation object.
- """
-
- bindable = False
- object_name = _('delegation')
- object_name_plural = _('delegations')
- label = _('Delegations')
- label_singular = _('Delegation')
-
- takes_params = (
- Str('aciname',
- cli_name='name',
- label=_('Delegation name'),
- doc=_('Delegation name'),
- primary_key=True,
- ),
- Str('permissions*',
- cli_name='permissions',
- label=_('Permissions'),
- doc=_('Permissions to grant (read, write). Default is write.'),
- ),
- Str('attrs+',
- cli_name='attrs',
- label=_('Attributes'),
- doc=_('Attributes to which the delegation applies'),
- normalizer=lambda value: value.lower(),
- ),
- Str('memberof',
- cli_name='membergroup',
- label=_('Member user group'),
- doc=_('User group to apply delegation to'),
- ),
- Str('group',
- cli_name='group',
- label=_('User group'),
- doc=_('User group ACI grants access to'),
- ),
- )
-
- def __json__(self):
- json_friendly_attributes = (
- 'label', 'label_singular', 'takes_params', 'bindable', 'name',
- 'object_name', 'object_name_plural',
- )
- json_dict = dict(
- (a, getattr(self, a)) for a in json_friendly_attributes
- )
- json_dict['primary_key'] = self.primary_key.name
-
- json_dict['methods'] = [m for m in self.methods]
- return json_dict
-
- def postprocess_result(self, result):
- try:
- # do not include prefix in result
- del result['aciprefix']
- except KeyError:
- pass
-
-
-
-@register()
-class delegation_add(crud.Create):
- __doc__ = _('Add a new delegation.')
-
- msg_summary = _('Added delegation "%(value)s"')
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- if not 'permissions' in kw:
- kw['permissions'] = (u'write',)
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_add'](aciname, **kw)['result']
- self.obj.postprocess_result(result)
-
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class delegation_del(crud.Delete):
- __doc__ = _('Delete a delegation.')
-
- has_output = output.standard_boolean
- msg_summary = _('Deleted delegation "%(value)s"')
-
- def execute(self, aciname, **kw):
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_del'](aciname, **kw)
- self.obj.postprocess_result(result)
- return dict(
- result=True,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class delegation_mod(crud.Update):
- __doc__ = _('Modify a delegation.')
-
- msg_summary = _('Modified delegation "%(value)s"')
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_mod'](aciname, **kw)['result']
- self.obj.postprocess_result(result)
-
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class delegation_find(crud.Search):
- __doc__ = _('Search for delegations.')
-
- msg_summary = ngettext(
- '%(count)d delegation matched', '%(count)d delegations matched', 0
- )
-
- takes_options = (gen_pkey_only_option("name"),)
- has_output_params = output_params
-
- def execute(self, term=None, **kw):
- kw['aciprefix'] = ACI_PREFIX
- results = api.Command['aci_find'](term, **kw)['result']
-
- for aci in results:
- self.obj.postprocess_result(aci)
-
- return dict(
- result=results,
- count=len(results),
- truncated=False,
- )
-
-
-
-@register()
-class delegation_show(crud.Retrieve):
- __doc__ = _('Display information about a delegation.')
-
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- result = api.Command['aci_show'](aciname, aciprefix=ACI_PREFIX, **kw)['result']
- self.obj.postprocess_result(result)
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
deleted file mode 100644
index 9cca07c6d..000000000
--- a/ipalib/plugins/dns.py
+++ /dev/null
@@ -1,4396 +0,0 @@
-# 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 absolute_import
-
-import netaddr
-import time
-import re
-import binascii
-import encodings.idna
-
-import dns.name
-import dns.exception
-import dns.rdatatype
-import dns.resolver
-import six
-
-from ipalib.dns import (get_record_rrtype,
- get_rrparam_from_part,
- has_cli_options,
- iterate_rrparams_by_parts,
- record_name_format)
-from ipalib.request import context
-from ipalib import api, errors, output
-from ipalib import Command
-from ipalib.capabilities import (
- VERSION_WITHOUT_CAPABILITIES,
- client_has_capability)
-from ipalib.parameters import (Flag, Bool, Int, Decimal, Str, StrEnum, Any,
- DNSNameParam)
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- LDAPObject,
- LDAPCreate,
- LDAPUpdate,
- LDAPSearch,
- LDAPQuery,
- LDAPDelete,
- LDAPRetrieve)
-from ipalib import _
-from ipalib import messages
-from ipalib.util import (normalize_zonemgr,
- get_dns_forward_zone_update_policy,
- get_dns_reverse_zone_update_policy,
- get_reverse_zone_default, REVERSE_DNS_ZONES,
- normalize_zone, validate_dnssec_global_forwarder,
- DNSSECSignatureMissingError, UnresolvableRecordError,
- EDNS0UnsupportedError, DNSSECValidationError,
- validate_dnssec_zone_forwarder_step1,
- validate_dnssec_zone_forwarder_step2,
- verify_host_resolvable)
-from ipapython.dn import DN
-from ipapython.ipautil import CheckedIPAddress
-from ipapython.dnsutil import check_zone_overlap
-from ipapython.dnsutil import DNSName
-from ipapython.dnsutil import related_to_auto_empty_zone
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Domain Name System (DNS)
-""") + _("""
-Manage DNS zone and resource records.
-""") + _("""
-SUPPORTED ZONE TYPES
-
- * Master zone (dnszone-*), contains authoritative data.
- * Forward zone (dnsforwardzone-*), forwards queries to configured forwarders
- (a set of DNS servers).
-""") + _("""
-USING STRUCTURED PER-TYPE OPTIONS
-""") + _("""
-There are many structured DNS RR types where DNS data stored in LDAP server
-is not just a scalar value, for example an IP address or a domain name, but
-a data structure which may be often complex. A good example is a LOC record
-[RFC1876] which consists of many mandatory and optional parts (degrees,
-minutes, seconds of latitude and longitude, altitude or precision).
-""") + _("""
-It may be difficult to manipulate such DNS records without making a mistake
-and entering an invalid value. DNS module provides an abstraction over these
-raw records and allows to manipulate each RR type with specific options. For
-each supported RR type, DNS module provides a standard option to manipulate
-a raw records with format --<rrtype>-rec, e.g. --mx-rec, and special options
-for every part of the RR structure with format --<rrtype>-<partname>, e.g.
---mx-preference and --mx-exchanger.
-""") + _("""
-When adding a record, either RR specific options or standard option for a raw
-value can be used, they just should not be combined in one add operation. When
-modifying an existing entry, new RR specific options can be used to change
-one part of a DNS record, where the standard option for raw value is used
-to specify the modified value. The following example demonstrates
-a modification of MX record preference from 0 to 1 in a record without
-modifying the exchanger:
-ipa dnsrecord-mod --mx-rec="0 mx.example.com." --mx-preference=1
-""") + _("""
-
-EXAMPLES:
-""") + _("""
- Add new zone:
- ipa dnszone-add example.com --admin-email=admin@example.com
-""") + _("""
- Add system permission that can be used for per-zone privilege delegation:
- ipa dnszone-add-permission example.com
-""") + _("""
- Modify the zone to allow dynamic updates for hosts own records in realm EXAMPLE.COM:
- ipa dnszone-mod example.com --dynamic-update=TRUE
-""") + _("""
- This is the equivalent of:
- ipa dnszone-mod example.com --dynamic-update=TRUE \\
- --update-policy="grant EXAMPLE.COM krb5-self * A; grant EXAMPLE.COM krb5-self * AAAA; grant EXAMPLE.COM krb5-self * SSHFP;"
-""") + _("""
- Modify the zone to allow zone transfers for local network only:
- ipa dnszone-mod example.com --allow-transfer=192.0.2.0/24
-""") + _("""
- Add new reverse zone specified by network IP address:
- ipa dnszone-add --name-from-ip=192.0.2.0/24
-""") + _("""
- Add second nameserver for example.com:
- ipa dnsrecord-add example.com @ --ns-rec=nameserver2.example.com
-""") + _("""
- Add a mail server for example.com:
- ipa dnsrecord-add example.com @ --mx-rec="10 mail1"
-""") + _("""
- Add another record using MX record specific options:
- ipa dnsrecord-add example.com @ --mx-preference=20 --mx-exchanger=mail2
-""") + _("""
- Add another record using interactive mode (started when dnsrecord-add, dnsrecord-mod,
- or dnsrecord-del are executed with no options):
- ipa dnsrecord-add example.com @
- Please choose a type of DNS resource record to be added
- The most common types for this type of zone are: NS, MX, LOC
-
- DNS resource record type: MX
- MX Preference: 30
- MX Exchanger: mail3
- Record name: example.com
- MX record: 10 mail1, 20 mail2, 30 mail3
- NS record: nameserver.example.com., nameserver2.example.com.
-""") + _("""
- Delete previously added nameserver from example.com:
- ipa dnsrecord-del example.com @ --ns-rec=nameserver2.example.com.
-""") + _("""
- Add LOC record for example.com:
- ipa dnsrecord-add example.com @ --loc-rec="49 11 42.4 N 16 36 29.6 E 227.64m"
-""") + _("""
- Add new A record for www.example.com. Create a reverse record in appropriate
- reverse zone as well. In this case a PTR record "2" pointing to www.example.com
- will be created in zone 2.0.192.in-addr.arpa.
- ipa dnsrecord-add example.com www --a-rec=192.0.2.2 --a-create-reverse
-""") + _("""
- Add new PTR record for www.example.com
- ipa dnsrecord-add 2.0.192.in-addr.arpa. 2 --ptr-rec=www.example.com.
-""") + _("""
- Add new SRV records for LDAP servers. Three quarters of the requests
- should go to fast.example.com, one quarter to slow.example.com. If neither
- is available, switch to backup.example.com.
- ipa dnsrecord-add example.com _ldap._tcp --srv-rec="0 3 389 fast.example.com"
- ipa dnsrecord-add example.com _ldap._tcp --srv-rec="0 1 389 slow.example.com"
- ipa dnsrecord-add example.com _ldap._tcp --srv-rec="1 1 389 backup.example.com"
-""") + _("""
- The interactive mode can be used for easy modification:
- ipa dnsrecord-mod example.com _ldap._tcp
- No option to modify specific record provided.
- Current DNS record contents:
-
- SRV record: 0 3 389 fast.example.com, 0 1 389 slow.example.com, 1 1 389 backup.example.com
-
- Modify SRV record '0 3 389 fast.example.com'? Yes/No (default No):
- Modify SRV record '0 1 389 slow.example.com'? Yes/No (default No): y
- SRV Priority [0]: (keep the default value)
- SRV Weight [1]: 2 (modified value)
- SRV Port [389]: (keep the default value)
- SRV Target [slow.example.com]: (keep the default value)
- 1 SRV record skipped. Only one value per DNS record type can be modified at one time.
- Record name: _ldap._tcp
- SRV record: 0 3 389 fast.example.com, 1 1 389 backup.example.com, 0 2 389 slow.example.com
-""") + _("""
- After this modification, three fifths of the requests should go to
- fast.example.com and two fifths to slow.example.com.
-""") + _("""
- An example of the interactive mode for dnsrecord-del command:
- ipa dnsrecord-del example.com www
- No option to delete specific record provided.
- Delete all? Yes/No (default No): (do not delete all records)
- Current DNS record contents:
-
- A record: 192.0.2.2, 192.0.2.3
-
- Delete A record '192.0.2.2'? Yes/No (default No):
- Delete A record '192.0.2.3'? Yes/No (default No): y
- Record name: www
- A record: 192.0.2.2 (A record 192.0.2.3 has been deleted)
-""") + _("""
- Show zone example.com:
- ipa dnszone-show example.com
-""") + _("""
- Find zone with "example" in its domain name:
- ipa dnszone-find example
-""") + _("""
- Find records for resources with "www" in their name in zone example.com:
- ipa dnsrecord-find example.com www
-""") + _("""
- Find A records with value 192.0.2.2 in zone example.com
- ipa dnsrecord-find example.com --a-rec=192.0.2.2
-""") + _("""
- Show records for resource www in zone example.com
- ipa dnsrecord-show example.com www
-""") + _("""
- Delegate zone sub.example to another nameserver:
- ipa dnsrecord-add example.com ns.sub --a-rec=203.0.113.1
- ipa dnsrecord-add example.com sub --ns-rec=ns.sub.example.com.
-""") + _("""
- Delete zone example.com with all resource records:
- ipa dnszone-del example.com
-""") + _("""
- If a global forwarder is configured, all queries for which this server is not
- authoritative (e.g. sub.example.com) will be routed to the global forwarder.
- Global forwarding configuration can be overridden per-zone.
-""") + _("""
- Semantics of forwarding in IPA matches BIND semantics and depends on the type
- of zone:
- * Master zone: local BIND replies authoritatively to queries for data in
- the given zone (including authoritative NXDOMAIN answers) and forwarding
- affects only queries for names below zone cuts (NS records) of locally
- served zones.
-
- * Forward zone: forward zone contains no authoritative data. BIND forwards
- queries, which cannot be answered from its local cache, to configured
- forwarders.
-""") + _("""
- Semantics of the --forwarder-policy option:
- * none - disable forwarding for the given zone.
- * first - forward all queries to configured forwarders. If they fail,
- do resolution using DNS root servers.
- * only - forward all queries to configured forwarders and if they fail,
- return failure.
-""") + _("""
- Disable global forwarding for given sub-tree:
- ipa dnszone-mod example.com --forward-policy=none
-""") + _("""
- This configuration forwards all queries for names outside the example.com
- sub-tree to global forwarders. Normal recursive resolution process is used
- for names inside the example.com sub-tree (i.e. NS records are followed etc.).
-""") + _("""
- Forward all requests for the zone external.example.com to another forwarder
- using a "first" policy (it will send the queries to the selected forwarder
- and if not answered it will use global root servers):
- ipa dnsforwardzone-add external.example.com --forward-policy=first \\
- --forwarder=203.0.113.1
-""") + _("""
- Change forward-policy for external.example.com:
- ipa dnsforwardzone-mod external.example.com --forward-policy=only
-""") + _("""
- Show forward zone external.example.com:
- ipa dnsforwardzone-show external.example.com
-""") + _("""
- List all forward zones:
- ipa dnsforwardzone-find
-""") + _("""
- Delete forward zone external.example.com:
- ipa dnsforwardzone-del external.example.com
-""") + _("""
- Resolve a host name to see if it exists (will add default IPA domain
- if one is not included):
- ipa dns-resolve www.example.com
- ipa dns-resolve www
-""") + _("""
-
-GLOBAL DNS CONFIGURATION
-""") + _("""
-DNS configuration passed to command line install script is stored in a local
-configuration file on each IPA server where DNS service is configured. These
-local settings can be overridden with a common configuration stored in LDAP
-server:
-""") + _("""
- Show global DNS configuration:
- ipa dnsconfig-show
-""") + _("""
- Modify global DNS configuration and set a list of global forwarders:
- ipa dnsconfig-mod --forwarder=203.0.113.113
-""")
-
-register = Registry()
-
-# supported resource record types
-_record_types = (
- u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV',
- u'DNAME', u'DS', u'HIP', u'HINFO', u'IPSECKEY', u'KEY', u'KX', u'LOC',
- u'MD', u'MINFO', u'MX', u'NAPTR', u'NS', u'NSEC', u'NXT', u'PTR', u'RRSIG',
- u'RP', u'SIG', u'SPF', u'SRV', u'SSHFP', u'TLSA', u'TXT',
-)
-
-# DNS zone record identificator
-_dns_zone_record = DNSName.empty
-
-# attributes derived from record types
-_record_attributes = [str(record_name_format % t.lower())
- for t in _record_types]
-
-# Deprecated
-# supported DNS classes, IN = internet, rest is almost never used
-_record_classes = (u'IN', u'CS', u'CH', u'HS')
-
-# IN record class
-_IN = dns.rdataclass.IN
-
-# NS record type
-_NS = dns.rdatatype.from_text('NS')
-
-_output_permissions = (
- output.summary,
- output.Output('result', bool, _('True means the operation was successful')),
- output.Output('value', unicode, _('Permission value')),
-)
-
-
-def _rname_validator(ugettext, zonemgr):
- try:
- DNSName(zonemgr) # test only if it is valid domain name
- except (ValueError, dns.exception.SyntaxError) as e:
- return unicode(e)
- return None
-
-def _create_zone_serial():
- """
- Generate serial number for zones. bind-dyndb-ldap expects unix time in
- to be used for SOA serial.
-
- SOA serial in a date format would also work, but it may be set to far
- future when many DNS updates are done per day (more than 100). Unix
- timestamp is more resilient to this issue.
- """
- return int(time.time())
-
-def _reverse_zone_name(netstr):
- try:
- netaddr.IPAddress(str(netstr))
- except (netaddr.AddrFormatError, ValueError):
- pass
- else:
- # use more sensible default prefix than netaddr default
- return unicode(get_reverse_zone_default(netstr))
-
- net = netaddr.IPNetwork(netstr)
- items = net.ip.reverse_dns.split('.')
- if net.version == 4:
- return u'.'.join(items[4 - net.prefixlen // 8:])
- elif net.version == 6:
- return u'.'.join(items[32 - net.prefixlen // 4:])
- else:
- return None
-
-def _validate_ipaddr(ugettext, ipaddr, ip_version=None):
- try:
- ip = netaddr.IPAddress(str(ipaddr), flags=netaddr.INET_PTON)
-
- if ip_version is not None:
- if ip.version != ip_version:
- return _('invalid IP address version (is %(value)d, must be %(required_value)d)!') \
- % dict(value=ip.version, required_value=ip_version)
- except (netaddr.AddrFormatError, ValueError):
- return _('invalid IP address format')
- return None
-
-def _validate_ip4addr(ugettext, ipaddr):
- return _validate_ipaddr(ugettext, ipaddr, 4)
-
-def _validate_ip6addr(ugettext, ipaddr):
- return _validate_ipaddr(ugettext, ipaddr, 6)
-
-def _validate_ipnet(ugettext, ipnet):
- try:
- net = netaddr.IPNetwork(ipnet)
- except (netaddr.AddrFormatError, ValueError, UnboundLocalError):
- return _('invalid IP network format')
- return None
-
-def _validate_bind_aci(ugettext, bind_acis):
- if not bind_acis:
- return
-
- bind_acis = bind_acis.split(';')
- if bind_acis[-1]:
- return _('each ACL element must be terminated with a semicolon')
- else:
- bind_acis.pop(-1)
-
- for bind_aci in bind_acis:
- if bind_aci in ("any", "none", "localhost", "localnets"):
- continue
-
- if bind_aci.startswith('!'):
- bind_aci = bind_aci[1:]
-
- try:
- ip = CheckedIPAddress(bind_aci, parse_netmask=True,
- allow_network=True, allow_loopback=True)
- except (netaddr.AddrFormatError, ValueError) as e:
- return unicode(e)
- except UnboundLocalError:
- return _(u"invalid address format")
-
-def _normalize_bind_aci(bind_acis):
- if not bind_acis:
- return
- bind_acis = bind_acis.split(';')
- normalized = []
- for bind_aci in bind_acis:
- if not bind_aci:
- continue
- if bind_aci in ("any", "none", "localhost", "localnets"):
- normalized.append(bind_aci)
- continue
-
- prefix = ""
- if bind_aci.startswith('!'):
- bind_aci = bind_aci[1:]
- prefix = "!"
-
- try:
- ip = CheckedIPAddress(bind_aci, parse_netmask=True,
- allow_network=True, allow_loopback=True)
- if '/' in bind_aci: # addr with netmask
- netmask = "/%s" % ip.prefixlen
- else:
- netmask = ""
- normalized.append(u"%s%s%s" % (prefix, str(ip), netmask))
- continue
- except Exception:
- normalized.append(bind_aci)
- continue
-
- acis = u';'.join(normalized)
- acis += u';'
- return acis
-
-def _validate_bind_forwarder(ugettext, forwarder):
- ip_address, sep, port = forwarder.partition(u' port ')
-
- ip_address_validation = _validate_ipaddr(ugettext, ip_address)
-
- if ip_address_validation is not None:
- return ip_address_validation
-
- if sep:
- try:
- port = int(port)
- if port < 0 or port > 65535:
- raise ValueError()
- except ValueError:
- return _('%(port)s is not a valid port' % dict(port=port))
-
- return None
-
-def _validate_nsec3param_record(ugettext, value):
- _nsec3param_pattern = (r'^(?P<alg>\d+) (?P<flags>\d+) (?P<iter>\d+) '
- r'(?P<salt>([0-9a-fA-F]{2})+|-)$')
- rec = re.compile(_nsec3param_pattern, flags=re.U)
- result = rec.match(value)
-
- if result is None:
- return _(u'expected format: <0-255> <0-255> <0-65535> '
- 'even-length_hexadecimal_digits_or_hyphen')
-
- alg = int(result.group('alg'))
- flags = int(result.group('flags'))
- iterations = int(result.group('iter'))
- salt = result.group('salt')
-
- if alg > 255:
- return _('algorithm value: allowed interval 0-255')
-
- if flags > 255:
- return _('flags value: allowed interval 0-255')
-
- if iterations > 65535:
- return _('iterations value: allowed interval 0-65535')
-
- if salt == u'-':
- return None
-
- try:
- binascii.a2b_hex(salt)
- except TypeError as e:
- return _('salt value: %(err)s') % {'err': e}
- return None
-
-
-def _hostname_validator(ugettext, value):
- assert isinstance(value, DNSName)
- if len(value.make_absolute().labels) < 3:
- return _('invalid domain-name: not fully qualified')
-
- return None
-
-def _no_wildcard_validator(ugettext, value):
- """Disallow usage of wildcards as RFC 4592 section 4 recommends
- """
- assert isinstance(value, DNSName)
- if value.is_wild():
- return _('should not be a wildcard domain name (RFC 4592 section 4)')
- return None
-
-def is_forward_record(zone, str_address):
- addr = netaddr.IPAddress(str_address)
- if addr.version == 4:
- result = api.Command['dnsrecord_find'](zone, arecord=str_address)
- elif addr.version == 6:
- result = api.Command['dnsrecord_find'](zone, aaaarecord=str_address)
- else:
- raise ValueError('Invalid address family')
-
- return result['count'] > 0
-
-def add_forward_record(zone, name, str_address):
- addr = netaddr.IPAddress(str_address)
- try:
- if addr.version == 4:
- api.Command['dnsrecord_add'](zone, name, arecord=str_address)
- elif addr.version == 6:
- api.Command['dnsrecord_add'](zone, name, aaaarecord=str_address)
- else:
- raise ValueError('Invalid address family')
- except errors.EmptyModlist:
- pass # the entry already exists and matches
-
-def get_reverse_zone(ipaddr):
- """
- resolve the reverse zone for IP address and see if it is managed by IPA
- server
- :param ipaddr: host IP address
- :return: tuple containing name of the reverse zone and the name of the
- record
- """
- ip = netaddr.IPAddress(str(ipaddr))
- revdns = DNSName(unicode(ip.reverse_dns))
- revzone = DNSName(dns.resolver.zone_for_name(revdns))
-
- try:
- api.Command['dnszone_show'](revzone)
- except errors.NotFound:
- raise errors.NotFound(
- reason=_(
- 'DNS reverse zone %(revzone)s for IP address '
- '%(addr)s is not managed by this server') % dict(
- addr=ipaddr, revzone=revzone)
- )
-
- revname = revdns.relativize(revzone)
-
- return revzone, revname
-
-def add_records_for_host_validation(option_name, host, domain, ip_addresses, check_forward=True, check_reverse=True):
- assert isinstance(host, DNSName)
- assert isinstance(domain, DNSName)
-
- try:
- api.Command['dnszone_show'](domain)['result']
- except errors.NotFound:
- raise errors.NotFound(
- reason=_('DNS zone %(zone)s not found') % dict(zone=domain)
- )
- if not isinstance(ip_addresses, (tuple, list)):
- ip_addresses = [ip_addresses]
-
- for ip_address in ip_addresses:
- try:
- ip = CheckedIPAddress(ip_address, match_local=False)
- except Exception as e:
- raise errors.ValidationError(name=option_name, error=unicode(e))
-
- if check_forward:
- if is_forward_record(domain, unicode(ip)):
- raise errors.DuplicateEntry(
- message=_(u'IP address %(ip)s is already assigned in domain %(domain)s.')\
- % dict(ip=str(ip), domain=domain))
-
- if check_reverse:
- try:
- # we prefer lookup of the IP through the reverse zone
- revzone, revname = get_reverse_zone(ip)
- reverse = api.Command['dnsrecord_find'](revzone, idnsname=revname)
- if reverse['count'] > 0:
- raise errors.DuplicateEntry(
- message=_(u'Reverse record for IP address %(ip)s already exists in reverse zone %(zone)s.')\
- % dict(ip=str(ip), zone=revzone))
- except errors.NotFound:
- pass
-
-
-def add_records_for_host(host, domain, ip_addresses, add_forward=True, add_reverse=True):
- assert isinstance(host, DNSName)
- assert isinstance(domain, DNSName)
-
- if not isinstance(ip_addresses, (tuple, list)):
- ip_addresses = [ip_addresses]
-
- for ip_address in ip_addresses:
- ip = CheckedIPAddress(ip_address, match_local=False)
-
- if add_forward:
- add_forward_record(domain, host, unicode(ip))
-
- if add_reverse:
- try:
- revzone, revname = get_reverse_zone(ip)
- addkw = {'ptrrecord': host.derelativize(domain).ToASCII()}
- api.Command['dnsrecord_add'](revzone, revname, **addkw)
- except errors.EmptyModlist:
- # the entry already exists and matches
- pass
-
-def _dns_name_to_string(value, raw=False):
- if isinstance(value, unicode):
- try:
- value = DNSName(value)
- except Exception:
- return value
-
- assert isinstance(value, DNSName)
- if raw:
- return value.ToASCII()
- else:
- return unicode(value)
-
-
-def _check_entry_objectclass(entry, objectclasses):
- """
- Check if entry contains all objectclasses
- """
- if not isinstance(objectclasses, (list, tuple)):
- objectclasses = [objectclasses, ]
- if not entry.get('objectclass'):
- return False
- entry_objectclasses = [o.lower() for o in entry['objectclass']]
- for o in objectclasses:
- if o not in entry_objectclasses:
- return False
- return True
-
-
-def _check_DN_objectclass(ldap, dn, objectclasses):
- try:
- entry = ldap.get_entry(dn, [u'objectclass', ])
- except Exception:
- return False
- else:
- return _check_entry_objectclass(entry, objectclasses)
-
-
-class DNSRecord(Str):
- # a list of parts that create the actual raw DNS record
- parts = None
- # an optional list of parameters used in record-specific operations
- extra = None
- supported = True
- # supported RR types: https://fedorahosted.org/bind-dyndb-ldap/browser/doc/schema
-
- label_format = _("%s record")
- part_label_format = "%s %s"
- doc_format = _('Raw %s records')
- option_group_format = _('%s Record')
- see_rfc_msg = _("(see RFC %s for details)")
- part_name_format = "%s_part_%s"
- extra_name_format = "%s_extra_%s"
- cli_name_format = "%s_%s"
- format_error_msg = None
-
- kwargs = Str.kwargs + (
- ('validatedns', bool, True),
- ('normalizedns', bool, True),
- )
-
- # should be replaced in subclasses
- rrtype = None
- rfc = None
-
- def __init__(self, name=None, *rules, **kw):
- if self.rrtype not in _record_types:
- raise ValueError("Unknown RR type: %s. Must be one of %s" % \
- (str(self.rrtype), ", ".join(_record_types)))
- if not name:
- name = "%s*" % (record_name_format % self.rrtype.lower())
- kw.setdefault('cli_name', '%s_rec' % self.rrtype.lower())
- kw.setdefault('label', self.label_format % self.rrtype)
- kw.setdefault('doc', self.doc_format % self.rrtype)
- kw.setdefault('option_group', self.option_group_format % self.rrtype)
-
- if not self.supported:
- kw['flags'] = ('no_option',)
-
- super(DNSRecord, self).__init__(name, *rules, **kw)
-
- def _get_part_values(self, value):
- values = value.split()
- if len(values) != len(self.parts):
- return None
- return tuple(values)
-
- def _part_values_to_string(self, values, idna=True):
- self._validate_parts(values)
- parts = []
- for v in values:
- if v is None:
- continue
- elif isinstance(v, DNSName) and idna:
- v = v.ToASCII()
- elif not isinstance(v, unicode):
- v = unicode(v)
- parts.append(v)
-
- return u" ".join(parts)
-
- def get_parts_from_kw(self, kw, raise_on_none=True):
- part_names = tuple(self.part_name_format % (self.rrtype.lower(), part.name) \
- for part in self.parts)
- vals = tuple(kw.get(part_name) for part_name in part_names)
-
- if all(val is None for val in vals):
- return
-
- if raise_on_none:
- for val_id,val in enumerate(vals):
- if val is None and self.parts[val_id].required:
- cli_name = self.cli_name_format % (self.rrtype.lower(), self.parts[val_id].name)
- raise errors.ConversionError(name=self.name,
- error=_("'%s' is a required part of DNS record") % cli_name)
-
- return vals
-
- def _validate_parts(self, parts):
- if len(parts) != len(self.parts):
- raise errors.ValidationError(name=self.name,
- error=_("Invalid number of parts!"))
-
- def _convert_scalar(self, value, index=None):
- if isinstance(value, (tuple, list)):
- return self._part_values_to_string(value)
- return super(DNSRecord, self)._convert_scalar(value)
-
- def normalize(self, value):
- if self.normalizedns:
- if isinstance(value, (tuple, list)):
- value = tuple(
- self._normalize_parts(v) for v in value \
- if v is not None
- )
- elif value is not None:
- value = (self._normalize_parts(value),)
-
- return super(DNSRecord, self).normalize(value)
-
- def _normalize_parts(self, value):
- """
- Normalize a DNS record value using normalizers for its parts.
- """
- if self.parts is None:
- return value
- try:
- values = self._get_part_values(value)
- if not values:
- return value
-
- converted_values = [ part._convert_scalar(values[part_id]) \
- if values[part_id] is not None else None
- for part_id, part in enumerate(self.parts)
- ]
-
- new_values = [ part.normalize(converted_values[part_id]) \
- for part_id, part in enumerate(self.parts) ]
-
- value = self._convert_scalar(new_values)
- except Exception:
- # cannot normalize, rather return original value than fail
- pass
- return value
-
- def _rule_validatedns(self, _, value):
- if not self.validatedns:
- return
-
- if value is None:
- return
-
- if not self.supported:
- return _('DNS RR type "%s" is not supported by bind-dyndb-ldap plugin') \
- % self.rrtype
-
- if self.parts is None:
- return
-
- # validate record format
- values = self._get_part_values(value)
- if not values:
- if not self.format_error_msg:
- part_names = [part.name.upper() for part in self.parts]
-
- if self.rfc:
- see_rfc_msg = " " + self.see_rfc_msg % self.rfc
- else:
- see_rfc_msg = ""
- return _('format must be specified as "%(format)s" %(rfcs)s') \
- % dict(format=" ".join(part_names), rfcs=see_rfc_msg)
- else:
- return self.format_error_msg
-
- # validate every part
- for part_id, part in enumerate(self.parts):
- val = part.normalize(values[part_id])
- val = part.convert(val)
- part.validate(val)
- return None
-
- def _convert_dnsrecord_part(self, part):
- """
- All parts of DNSRecord need to be processed and modified before they
- can be added to global DNS API. For example a prefix need to be added
- before part name so that the name is unique in the global namespace.
- """
- name = self.part_name_format % (self.rrtype.lower(), part.name)
- cli_name = self.cli_name_format % (self.rrtype.lower(), part.name)
- label = self.part_label_format % (self.rrtype, unicode(part.label))
- option_group = self.option_group_format % self.rrtype
- flags = list(part.flags) + ['dnsrecord_part', 'virtual_attribute',]
- if not part.required:
- flags.append('dnsrecord_optional')
- if not self.supported:
- flags.append("no_option")
-
- return part.clone_rename(name,
- cli_name=cli_name,
- label=label,
- required=False,
- option_group=option_group,
- flags=flags,
- hint=self.name,) # name of parent RR param
-
- def _convert_dnsrecord_extra(self, extra):
- """
- Parameters for special per-type behavior need to be processed in the
- same way as record parts in _convert_dnsrecord_part().
- """
- name = self.extra_name_format % (self.rrtype.lower(), extra.name)
- cli_name = self.cli_name_format % (self.rrtype.lower(), extra.name)
- label = self.part_label_format % (self.rrtype, unicode(extra.label))
- option_group = self.option_group_format % self.rrtype
- flags = list(extra.flags) + ['dnsrecord_extra', 'virtual_attribute',]
-
- return extra.clone_rename(name,
- cli_name=cli_name,
- label=label,
- required=False,
- option_group=option_group,
- flags=flags,
- hint=self.name,) # name of parent RR param
-
- def get_parts(self):
- if self.parts is None:
- return tuple()
-
- return tuple(self._convert_dnsrecord_part(part) for part in self.parts)
-
- def get_extra(self):
- if self.extra is None:
- return tuple()
-
- return tuple(self._convert_dnsrecord_extra(extra) for extra in self.extra)
-
- # callbacks for per-type special record behavior
- def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
-class ForwardRecord(DNSRecord):
- extra = (
- Flag('create_reverse?',
- label=_('Create reverse'),
- doc=_('Create reverse record for this IP Address'),
- flags=['no_update']
- ),
- )
-
- def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- reverse_option = self._convert_dnsrecord_extra(self.extra[0])
- if options.get(reverse_option.name):
- records = entry_attrs.get(self.name, [])
- if not records:
- # --<rrtype>-create-reverse is set, but there are not records
- raise errors.RequirementError(name=self.name)
-
- for record in records:
- add_records_for_host_validation(self.name, keys[-1], keys[-2], record,
- check_forward=False,
- check_reverse=True)
-
- setattr(context, '%s_reverse' % self.name, entry_attrs.get(self.name))
-
- def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- rev_records = getattr(context, '%s_reverse' % self.name, [])
-
- if rev_records:
- # make sure we don't run this post callback action again in nested
- # commands, line adding PTR record in add_records_for_host
- delattr(context, '%s_reverse' % self.name)
- for record in rev_records:
- try:
- add_records_for_host(keys[-1], keys[-2], record,
- add_forward=False, add_reverse=True)
- except Exception as e:
- raise errors.NonFatalError(
- reason=_('Cannot create reverse record for "%(value)s": %(exc)s') \
- % dict(value=record, exc=unicode(e)))
-
-class UnsupportedDNSRecord(DNSRecord):
- """
- Records which are not supported by IPA CLI, but we allow to show them if
- LDAP contains these records.
- """
- supported = False
-
- def _get_part_values(self, value):
- return tuple()
-
-
-class ARecord(ForwardRecord):
- rrtype = 'A'
- rfc = 1035
- parts = (
- Str('ip_address',
- _validate_ip4addr,
- label=_('IP Address'),
- ),
- )
-
-class A6Record(DNSRecord):
- rrtype = 'A6'
- rfc = 3226
- parts = (
- Str('data',
- label=_('Record data'),
- ),
- )
-
- def _get_part_values(self, value):
- # A6 RR type is obsolete and only a raw interface is provided
- return (value,)
-
-class AAAARecord(ForwardRecord):
- rrtype = 'AAAA'
- rfc = 3596
- parts = (
- Str('ip_address',
- _validate_ip6addr,
- label=_('IP Address'),
- ),
- )
-
-class AFSDBRecord(DNSRecord):
- rrtype = 'AFSDB'
- rfc = 1183
- parts = (
- Int('subtype?',
- label=_('Subtype'),
- minvalue=0,
- maxvalue=65535,
- ),
- DNSNameParam('hostname',
- label=_('Hostname'),
- ),
- )
-
-class APLRecord(UnsupportedDNSRecord):
- rrtype = 'APL'
- rfc = 3123
-
-class CERTRecord(DNSRecord):
- rrtype = 'CERT'
- rfc = 4398
- parts = (
- Int('type',
- label=_('Certificate Type'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('key_tag',
- label=_('Key Tag'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('algorithm',
- label=_('Algorithm'),
- minvalue=0,
- maxvalue=255,
- ),
- Str('certificate_or_crl',
- label=_('Certificate/CRL'),
- ),
- )
-
-class CNAMERecord(DNSRecord):
- rrtype = 'CNAME'
- rfc = 1035
- parts = (
- DNSNameParam('hostname',
- label=_('Hostname'),
- doc=_('A hostname which this alias hostname points to'),
- ),
- )
-
-class DHCIDRecord(UnsupportedDNSRecord):
- rrtype = 'DHCID'
- rfc = 4701
-
-class DNAMERecord(DNSRecord):
- rrtype = 'DNAME'
- rfc = 2672
- parts = (
- DNSNameParam('target',
- label=_('Target'),
- ),
- )
-
-
-class DSRecord(DNSRecord):
- rrtype = 'DS'
- rfc = 4034
- parts = (
- Int('key_tag',
- label=_('Key Tag'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('algorithm',
- label=_('Algorithm'),
- minvalue=0,
- maxvalue=255,
- ),
- Int('digest_type',
- label=_('Digest Type'),
- minvalue=0,
- maxvalue=255,
- ),
- Str('digest',
- label=_('Digest'),
- pattern=r'^[0-9a-fA-F]+$',
- pattern_errmsg=u'only hexadecimal digits are allowed'
- ),
- )
-
-
-class DLVRecord(DSRecord):
- # must use same attributes as DSRecord
- rrtype = 'DLV'
- rfc = 4431
-
-
-class HINFORecord(UnsupportedDNSRecord):
- rrtype = 'HINFO'
- rfc = 1035
-
-
-class HIPRecord(UnsupportedDNSRecord):
- rrtype = 'HIP'
- rfc = 5205
-
-class KEYRecord(UnsupportedDNSRecord):
- # managed by BIND itself
- rrtype = 'KEY'
- rfc = 2535
-
-class IPSECKEYRecord(UnsupportedDNSRecord):
- rrtype = 'IPSECKEY'
- rfc = 4025
-
-class KXRecord(DNSRecord):
- rrtype = 'KX'
- rfc = 2230
- parts = (
- Int('preference',
- label=_('Preference'),
- doc=_('Preference given to this exchanger. Lower values are more preferred'),
- minvalue=0,
- maxvalue=65535,
- ),
- DNSNameParam('exchanger',
- label=_('Exchanger'),
- doc=_('A host willing to act as a key exchanger'),
- ),
- )
-
-class LOCRecord(DNSRecord):
- rrtype = 'LOC'
- rfc = 1876
- parts = (
- Int('lat_deg',
- label=_('Degrees Latitude'),
- minvalue=0,
- maxvalue=90,
- ),
- Int('lat_min?',
- label=_('Minutes Latitude'),
- minvalue=0,
- maxvalue=59,
- ),
- Decimal('lat_sec?',
- label=_('Seconds Latitude'),
- minvalue='0.0',
- maxvalue='59.999',
- precision=3,
- ),
- StrEnum('lat_dir',
- label=_('Direction Latitude'),
- values=(u'N', u'S',),
- ),
- Int('lon_deg',
- label=_('Degrees Longitude'),
- minvalue=0,
- maxvalue=180,
- ),
- Int('lon_min?',
- label=_('Minutes Longitude'),
- minvalue=0,
- maxvalue=59,
- ),
- Decimal('lon_sec?',
- label=_('Seconds Longitude'),
- minvalue='0.0',
- maxvalue='59.999',
- precision=3,
- ),
- StrEnum('lon_dir',
- label=_('Direction Longitude'),
- values=(u'E', u'W',),
- ),
- Decimal('altitude',
- label=_('Altitude'),
- minvalue='-100000.00',
- maxvalue='42849672.95',
- precision=2,
- ),
- Decimal('size?',
- label=_('Size'),
- minvalue='0.0',
- maxvalue='90000000.00',
- precision=2,
- ),
- Decimal('h_precision?',
- label=_('Horizontal Precision'),
- minvalue='0.0',
- maxvalue='90000000.00',
- precision=2,
- ),
- Decimal('v_precision?',
- label=_('Vertical Precision'),
- minvalue='0.0',
- maxvalue='90000000.00',
- precision=2,
- ),
- )
-
- format_error_msg = _("""format must be specified as
- "d1 [m1 [s1]] {"N"|"S"} d2 [m2 [s2]] {"E"|"W"} alt["m"] [siz["m"] [hp["m"] [vp["m"]]]]"
- where:
- d1: [0 .. 90] (degrees latitude)
- d2: [0 .. 180] (degrees longitude)
- m1, m2: [0 .. 59] (minutes latitude/longitude)
- s1, s2: [0 .. 59.999] (seconds latitude/longitude)
- alt: [-100000.00 .. 42849672.95] BY .01 (altitude in meters)
- siz, hp, vp: [0 .. 90000000.00] (size/precision in meters)
- See RFC 1876 for details""")
-
- def _get_part_values(self, value):
- 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 None:
- return None
-
- return tuple(x.strip() if x is not None else x for x in m.groups())
-
- def _validate_parts(self, parts):
- super(LOCRecord, self)._validate_parts(parts)
-
- # create part_name -> part_id map first
- part_name_map = dict((part.name, part_id) \
- for part_id,part in enumerate(self.parts))
-
- requirements = ( ('lat_sec', 'lat_min'),
- ('lon_sec', 'lon_min'),
- ('h_precision', 'size'),
- ('v_precision', 'h_precision', 'size') )
-
- for req in requirements:
- target_part = req[0]
-
- if parts[part_name_map[target_part]] is not None:
- required_parts = req[1:]
- if any(parts[part_name_map[part]] is None for part in required_parts):
- target_cli_name = self.cli_name_format % (self.rrtype.lower(), req[0])
- required_cli_names = [ self.cli_name_format % (self.rrtype.lower(), part)
- for part in req[1:] ]
- error = _("'%(required)s' must not be empty when '%(name)s' is set") % \
- dict(required=', '.join(required_cli_names),
- name=target_cli_name)
- raise errors.ValidationError(name=self.name, error=error)
-
-
-class MDRecord(UnsupportedDNSRecord):
- # obsoleted, use MX instead
- rrtype = 'MD'
- rfc = 1035
-
-
-class MINFORecord(UnsupportedDNSRecord):
- rrtype = 'MINFO'
- rfc = 1035
-
-
-class MXRecord(DNSRecord):
- rrtype = 'MX'
- rfc = 1035
- parts = (
- Int('preference',
- label=_('Preference'),
- doc=_('Preference given to this exchanger. Lower values are more preferred'),
- minvalue=0,
- maxvalue=65535,
- ),
- DNSNameParam('exchanger',
- label=_('Exchanger'),
- doc=_('A host willing to act as a mail exchanger'),
- ),
- )
-
-class NSRecord(DNSRecord):
- rrtype = 'NS'
- rfc = 1035
-
- parts = (
- DNSNameParam('hostname',
- label=_('Hostname'),
- ),
- )
-
-class NSECRecord(UnsupportedDNSRecord):
- # managed by BIND itself
- rrtype = 'NSEC'
- rfc = 4034
-
-
-def _validate_naptr_flags(ugettext, flags):
- allowed_flags = u'SAUP'
- flags = flags.replace('"','').replace('\'','')
-
- for flag in flags:
- if flag not in allowed_flags:
- return _('flags must be one of "S", "A", "U", or "P"')
-
-class NAPTRRecord(DNSRecord):
- rrtype = 'NAPTR'
- rfc = 2915
-
- parts = (
- Int('order',
- label=_('Order'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('preference',
- label=_('Preference'),
- minvalue=0,
- maxvalue=65535,
- ),
- Str('flags',
- _validate_naptr_flags,
- label=_('Flags'),
- normalizer=lambda x:x.upper()
- ),
- Str('service',
- label=_('Service'),
- ),
- Str('regexp',
- label=_('Regular Expression'),
- ),
- Str('replacement',
- label=_('Replacement'),
- ),
- )
-
-
-class NXTRecord(UnsupportedDNSRecord):
- rrtype = 'NXT'
- rfc = 2535
-
-
-class PTRRecord(DNSRecord):
- rrtype = 'PTR'
- rfc = 1035
- parts = (
- DNSNameParam('hostname',
- #RFC 2317 section 5.2 -- can be relative
- label=_('Hostname'),
- doc=_('The hostname this reverse record points to'),
- ),
- )
-
-class RPRecord(UnsupportedDNSRecord):
- rrtype = 'RP'
- rfc = 1183
-
-class SRVRecord(DNSRecord):
- rrtype = 'SRV'
- rfc = 2782
- parts = (
- Int('priority',
- label=_('Priority'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('weight',
- label=_('Weight'),
- minvalue=0,
- maxvalue=65535,
- ),
- Int('port',
- label=_('Port'),
- minvalue=0,
- maxvalue=65535,
- ),
- DNSNameParam('target',
- label=_('Target'),
- doc=_('The domain name of the target host or \'.\' if the service is decidedly not available at this domain'),
- ),
- )
-
-def _sig_time_validator(ugettext, value):
- time_format = "%Y%m%d%H%M%S"
- try:
- time.strptime(value, time_format)
- except ValueError:
- return _('the value does not follow "YYYYMMDDHHMMSS" time format')
-
-
-class SIGRecord(UnsupportedDNSRecord):
- # managed by BIND itself
- rrtype = 'SIG'
- rfc = 2535
-
-class SPFRecord(UnsupportedDNSRecord):
- rrtype = 'SPF'
- rfc = 4408
-
-class RRSIGRecord(UnsupportedDNSRecord):
- # managed by BIND itself
- rrtype = 'RRSIG'
- rfc = 4034
-
-class SSHFPRecord(DNSRecord):
- rrtype = 'SSHFP'
- rfc = 4255
- parts = (
- Int('algorithm',
- label=_('Algorithm'),
- minvalue=0,
- maxvalue=255,
- ),
- Int('fp_type',
- label=_('Fingerprint Type'),
- minvalue=0,
- maxvalue=255,
- ),
- Str('fingerprint',
- label=_('Fingerprint'),
- ),
- )
-
- def _get_part_values(self, value):
- # fingerprint part can contain space in LDAP, return it as one part
- values = value.split(None, 2)
- if len(values) != len(self.parts):
- return None
- return tuple(values)
-
-
-class TLSARecord(DNSRecord):
- rrtype = 'TLSA'
- rfc = 6698
- parts = (
- Int('cert_usage',
- label=_('Certificate Usage'),
- minvalue=0,
- maxvalue=255,
- ),
- Int('selector',
- label=_('Selector'),
- minvalue=0,
- maxvalue=255,
- ),
- Int('matching_type',
- label=_('Matching Type'),
- minvalue=0,
- maxvalue=255,
- ),
- Str('cert_association_data',
- label=_('Certificate Association Data'),
- ),
- )
-
-
-class TXTRecord(DNSRecord):
- rrtype = 'TXT'
- rfc = 1035
- parts = (
- Str('data',
- label=_('Text Data'),
- ),
- )
-
- def _get_part_values(self, value):
- # ignore any space in TXT record
- return (value,)
-
-_dns_records = (
- ARecord(),
- AAAARecord(),
- A6Record(),
- AFSDBRecord(),
- APLRecord(),
- CERTRecord(),
- CNAMERecord(),
- DHCIDRecord(),
- DLVRecord(),
- DNAMERecord(),
- DSRecord(),
- HIPRecord(),
- IPSECKEYRecord(),
- KEYRecord(),
- KXRecord(),
- LOCRecord(),
- MXRecord(),
- NAPTRRecord(),
- NSRecord(),
- NSECRecord(),
- PTRRecord(),
- RRSIGRecord(),
- RPRecord(),
- SIGRecord(),
- SPFRecord(),
- SRVRecord(),
- SSHFPRecord(),
- TLSARecord(),
- TXTRecord(),
-)
-
-def __dns_record_options_iter():
- for opt in (Any('dnsrecords?',
- label=_('Records'),
- flags=['no_create', 'no_search', 'no_update'],),
- Str('dnstype?',
- label=_('Record type'),
- flags=['no_create', 'no_search', 'no_update'],),
- Str('dnsdata?',
- label=_('Record data'),
- flags=['no_create', 'no_search', 'no_update'],)):
- # These 3 options are used in --structured format. They are defined
- # rather in takes_params than has_output_params because of their
- # order - they should be printed to CLI before any DNS part param
- yield opt
- for option in _dns_records:
- yield option
-
- for part in option.get_parts():
- yield part
-
- for extra in option.get_extra():
- yield extra
-
-_dns_record_options = tuple(__dns_record_options_iter())
-
-
-def check_ns_rec_resolvable(zone, name, log):
- assert isinstance(zone, DNSName)
- assert isinstance(name, DNSName)
-
- if name.is_empty():
- name = zone.make_absolute()
- elif not name.is_absolute():
- # this is a DNS name relative to the zone
- name = name.derelativize(zone.make_absolute())
- try:
- verify_host_resolvable(name)
- except errors.DNSNotARecordError:
- raise errors.NotFound(
- reason=_('Nameserver \'%(host)s\' does not have a corresponding '
- 'A/AAAA record') % {'host': name}
- )
-
-def dns_container_exists(ldap):
- try:
- ldap.get_entry(DN(api.env.container_dns, api.env.basedn), [])
- except errors.NotFound:
- return False
- return True
-
-
-def dnssec_installed(ldap):
- """
- * Method opendnssecinstance.get_dnssec_key_masters() CANNOT be used in the
- dns plugin, or any plugin accessible for common users! *
- Why?: The content of service container is not readable for common users.
-
- This method only try to find if a DNSSEC service container exists on any
- replica. What means that DNSSEC key master is installed.
- :param ldap: ldap connection
- :return: True if DNSSEC was installed, otherwise False
- """
- dn = DN(api.env.container_masters, api.env.basedn)
-
- filter_attrs = {
- u'cn': u'DNSSEC',
- u'objectclass': u'ipaConfigObject',
- }
- only_masters_f = ldap.make_filter(filter_attrs, rules=ldap.MATCH_ALL)
-
- try:
- ldap.find_entries(filter=only_masters_f, base_dn=dn)
- except errors.NotFound:
- return False
- return True
-
-
-def default_zone_update_policy(zone):
- if zone.is_reverse():
- return get_dns_reverse_zone_update_policy(api.env.realm, zone.ToASCII())
- else:
- return get_dns_forward_zone_update_policy(api.env.realm)
-
-dnszone_output_params = (
- Str('managedby',
- label=_('Managedby permission'),
- ),
-)
-
-
-def _convert_to_idna(value):
- """
- Function converts a unicode value to idna, without extra validation.
- If conversion fails, None is returned
- """
- assert isinstance(value, unicode)
-
- try:
- idna_val = value
- start_dot = u''
- end_dot = u''
- if idna_val.startswith(u'.'):
- idna_val = idna_val[1:]
- start_dot = u'.'
- if idna_val.endswith(u'.'):
- idna_val = idna_val[:-1]
- end_dot = u'.'
- idna_val = encodings.idna.nameprep(idna_val)
- idna_val = re.split(r'(?<!\\)\.', idna_val)
- idna_val = u'%s%s%s' % (start_dot,
- u'.'.join(encodings.idna.ToASCII(x)
- for x in idna_val),
- end_dot)
- return idna_val
- except Exception:
- pass
- return None
-
-
-def _create_idn_filter(cmd, ldap, term=None, **options):
- if term:
- #include idna values to search
- term_idna = _convert_to_idna(term)
- if term_idna and term != term_idna:
- term = (term, term_idna)
-
- search_kw = {}
- attr_extra_filters = []
-
- for attr, value in cmd.args_options_2_entry(**options).items():
- if not isinstance(value, list):
- value = [value]
- for i, v in enumerate(value):
- if isinstance(v, DNSName):
- value[i] = v.ToASCII()
- elif attr in map_names_to_records:
- record = map_names_to_records[attr]
- parts = record._get_part_values(v)
- if parts is None:
- value[i] = v
- continue
- try:
- value[i] = record._part_values_to_string(parts)
- except errors.ValidationError:
- value[i] = v
-
- #create MATCH_ANY filter for multivalue
- if len(value) > 1:
- f = ldap.make_filter({attr: value}, rules=ldap.MATCH_ANY)
- attr_extra_filters.append(f)
- else:
- search_kw[attr] = value
-
- if cmd.obj.search_attributes:
- search_attrs = cmd.obj.search_attributes
- else:
- search_attrs = cmd.obj.default_attributes
- if cmd.obj.search_attributes_config:
- config = ldap.get_ipa_config()
- config_attrs = config.get(cmd.obj.search_attributes_config, [])
- if len(config_attrs) == 1 and (isinstance(config_attrs[0],
- six.string_types)):
- search_attrs = config_attrs[0].split(',')
-
- search_kw['objectclass'] = cmd.obj.object_class
- attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
- if attr_extra_filters:
- #combine filter if there is any idna value
- attr_extra_filters.append(attr_filter)
- attr_filter = ldap.combine_filters(attr_extra_filters,
- rules=ldap.MATCH_ALL)
-
- search_kw = {}
- for a in search_attrs:
- search_kw[a] = term
- term_filter = ldap.make_filter(search_kw, exact=False)
-
- member_filter = cmd.get_member_filter(ldap, **options)
-
- filter = ldap.combine_filters(
- (term_filter, attr_filter, member_filter), rules=ldap.MATCH_ALL
- )
- return filter
-
-
-map_names_to_records = {record_name_format % record.rrtype.lower(): record
- for record in _dns_records if record.supported}
-
-def _records_idn_postprocess(record, **options):
- for attr in record.keys():
- attr = attr.lower()
- try:
- param = map_names_to_records[attr]
- except KeyError:
- continue
- if not isinstance(param, DNSRecord):
- continue
-
- part_params = param.get_parts()
- rrs = []
- for dnsvalue in record[attr]:
- parts = param._get_part_values(dnsvalue)
- if parts is None:
- continue
- parts = list(parts)
- try:
- for (i, p) in enumerate(parts):
- if isinstance(part_params[i], DNSNameParam):
- parts[i] = DNSName(p)
- rrs.append(param._part_values_to_string(parts,
- idna=options.get('raw', False)))
- except (errors.ValidationError, errors.ConversionError):
- rrs.append(dnsvalue)
- record[attr] = rrs
-
-def _normalize_zone(zone):
- if isinstance(zone, unicode):
- # normalize only non-IDNA zones
- try:
- zone.encode('ascii')
- except UnicodeError:
- pass
- else:
- return zone.lower()
- return zone
-
-
-def _get_auth_zone_ldap(api, name):
- """
- Find authoritative zone in LDAP for name. Only active zones are considered.
- :param name:
- :return: (zone, truncated)
- zone: authoritative zone, or None if authoritative zone is not in LDAP
- """
- assert isinstance(name, DNSName)
- ldap = api.Backend.ldap2
-
- # Create all possible parent zone names
- search_name = name.make_absolute()
- zone_names = []
- for i, name in enumerate(search_name):
- zone_name_abs = DNSName(search_name[i:]).ToASCII()
- zone_names.append(zone_name_abs)
- # compatibility with IPA < 4.0, zone name can be relative
- zone_names.append(zone_name_abs[:-1])
-
- # Create filters
- objectclass_filter = ldap.make_filter({'objectclass':'idnszone'})
- zonenames_filter = ldap.make_filter({'idnsname': zone_names})
- zoneactive_filter = ldap.make_filter({'idnsZoneActive': 'true'})
- complete_filter = ldap.combine_filters(
- [objectclass_filter, zonenames_filter, zoneactive_filter],
- rules=ldap.MATCH_ALL
- )
-
- try:
- entries, truncated = ldap.find_entries(
- filter=complete_filter,
- attrs_list=['idnsname'],
- base_dn=DN(api.env.container_dns, api.env.basedn),
- scope=ldap.SCOPE_ONELEVEL
- )
- except errors.NotFound:
- return None, False
-
- # always use absolute zones
- matched_auth_zones = [entry.single_value['idnsname'].make_absolute()
- for entry in entries]
-
- # return longest match
- return max(matched_auth_zones, key=len), truncated
-
-
-def _get_longest_match_ns_delegation_ldap(api, zone, name):
- """
- Searches for deepest delegation for name in LDAP zone.
-
- NOTE: NS record in zone apex is not considered as delegation.
- It returns None if there is no delegation outside of zone apex.
-
- Example:
- zone: example.com.
- name: ns.sub.example.com.
-
- records:
- extra.ns.sub.example.com.
- sub.example.com.
- example.com
-
- result: sub.example.com.
-
- :param zone: zone name
- :param name:
- :return: (match, truncated);
- match: delegation name if success, or None if no delegation record exists
- """
- assert isinstance(zone, DNSName)
- assert isinstance(name, DNSName)
-
- ldap = api.Backend.ldap2
-
- # get zone DN
- zone_dn = api.Object.dnszone.get_dn(zone)
-
- if name.is_absolute():
- relative_record_name = name.relativize(zone.make_absolute())
- else:
- relative_record_name = name
-
- # Name is zone apex
- if relative_record_name.is_empty():
- return None, False
-
- # create list of possible record names
- possible_record_names = [DNSName(relative_record_name[i:]).ToASCII()
- for i in range(len(relative_record_name))]
-
- # search filters
- name_filter = ldap.make_filter({'idnsname': [possible_record_names]})
- objectclass_filter = ldap.make_filter({'objectclass': 'idnsrecord'})
- complete_filter = ldap.combine_filters(
- [name_filter, objectclass_filter],
- rules=ldap.MATCH_ALL
- )
-
- try:
- entries, truncated = ldap.find_entries(
- filter=complete_filter,
- attrs_list=['idnsname', 'nsrecord'],
- base_dn=zone_dn,
- scope=ldap.SCOPE_ONELEVEL
- )
- except errors.NotFound:
- return None, False
-
- matched_records = []
-
- # test if entry contains NS records
- for entry in entries:
- if entry.get('nsrecord'):
- matched_records.append(entry.single_value['idnsname'])
-
- if not matched_records:
- return None, truncated
-
- # return longest match
- return max(matched_records, key=len), truncated
-
-
-def _find_subtree_forward_zones_ldap(api, name, child_zones_only=False):
- """
- Search for forwardzone <name> and all child forwardzones
- Filter: (|(*.<name>.)(<name>.))
- :param name:
- :param child_zones_only: search only for child zones
- :return: (list of zonenames, truncated), list is empty if no zone found
- """
- assert isinstance(name, DNSName)
- ldap = api.Backend.ldap2
-
- # prepare for filter "*.<name>."
- search_name = u".%s" % name.make_absolute().ToASCII()
-
- # we need to search zone with and without last dot, due compatibility
- # with IPA < 4.0
- search_names = [search_name, search_name[:-1]]
-
- # Create filters
- objectclass_filter = ldap.make_filter({'objectclass':'idnsforwardzone'})
- zonenames_filter = ldap.make_filter({'idnsname': search_names}, exact=False,
- trailing_wildcard=False)
- if not child_zones_only:
- # find also zone with exact name
- exact_name = name.make_absolute().ToASCII()
- # we need to search zone with and without last dot, due compatibility
- # with IPA < 4.0
- exact_names = [exact_name, exact_name[-1]]
- exact_name_filter = ldap.make_filter({'idnsname': exact_names})
- zonenames_filter = ldap.combine_filters([zonenames_filter,
- exact_name_filter])
-
- zoneactive_filter = ldap.make_filter({'idnsZoneActive': 'true'})
- complete_filter = ldap.combine_filters(
- [objectclass_filter, zonenames_filter, zoneactive_filter],
- rules=ldap.MATCH_ALL
- )
-
- try:
- entries, truncated = ldap.find_entries(
- filter=complete_filter,
- attrs_list=['idnsname'],
- base_dn=DN(api.env.container_dns, api.env.basedn),
- scope=ldap.SCOPE_ONELEVEL
- )
- except errors.NotFound:
- return [], False
-
- result = [entry.single_value['idnsname'].make_absolute()
- for entry in entries]
-
- return result, truncated
-
-
-def _get_zone_which_makes_fw_zone_ineffective(api, fwzonename):
- """
- Check if forward zone is effective.
-
- If parent zone exists as authoritative zone, the forward zone will not
- forward queries by default. It is necessary to delegate authority
- to forward zone with a NS record.
-
- Example:
-
- Forward zone: sub.example.com
- Zone: example.com
-
- Forwarding will not work, because the server thinks it is authoritative
- for zone and will return NXDOMAIN
-
- Adding record: sub.example.com NS ns.sub.example.com.
- will delegate authority, and IPA DNS server will forward DNS queries.
-
- :param fwzonename: forwardzone
- :return: (zone, truncated)
- zone: None if effective, name of authoritative zone otherwise
- """
- assert isinstance(fwzonename, DNSName)
-
- auth_zone, truncated_zone = _get_auth_zone_ldap(api, fwzonename)
- if not auth_zone:
- return None, truncated_zone
-
- delegation_record_name, truncated_ns =\
- _get_longest_match_ns_delegation_ldap(api, auth_zone, fwzonename)
-
- truncated = truncated_ns or truncated_zone
-
- if delegation_record_name:
- return None, truncated
-
- return auth_zone, truncated
-
-
-def _add_warning_fw_zone_is_not_effective(api, result, fwzone, version):
- """
- Adds warning message to result, if required
- """
- authoritative_zone, truncated = \
- _get_zone_which_makes_fw_zone_ineffective(api, fwzone)
- if authoritative_zone:
- # forward zone is not effective and forwarding will not work
- messages.add_message(
- version, result,
- messages.ForwardzoneIsNotEffectiveWarning(
- fwzone=fwzone, authzone=authoritative_zone,
- ns_rec=fwzone.relativize(authoritative_zone)
- )
- )
-
-
-def _add_warning_fw_policy_conflict_aez(result, fwzone, **options):
- """Warn if forwarding policy conflicts with an automatic empty zone."""
- fwd_policy = result['result'].get(u'idnsforwardpolicy',
- dnsforwardzone.default_forward_policy)
- if (
- fwd_policy != [u'only']
- and related_to_auto_empty_zone(DNSName(fwzone))
- ):
- messages.add_message(
- options['version'], result,
- messages.DNSForwardPolicyConflictWithEmptyZone()
- )
-
-
-class DNSZoneBase(LDAPObject):
- """
- Base class for DNS Zone
- """
- container_dn = api.env.container_dns
- object_class = ['top']
- possible_objectclasses = ['ipadnszone']
- default_attributes = [
- 'idnsname', 'idnszoneactive', 'idnsforwarders', 'idnsforwardpolicy'
- ]
-
- takes_params = (
- DNSNameParam('idnsname',
- _no_wildcard_validator, # RFC 4592 section 4
- only_absolute=True,
- cli_name='name',
- label=_('Zone name'),
- doc=_('Zone name (FQDN)'),
- default_from=lambda name_from_ip: _reverse_zone_name(name_from_ip),
- normalizer=_normalize_zone,
- primary_key=True,
- ),
- Str('name_from_ip?', _validate_ipnet,
- label=_('Reverse zone IP network'),
- doc=_('IP network to create reverse zone name from'),
- flags=('virtual_attribute',),
- ),
- Bool('idnszoneactive?',
- cli_name='zone_active',
- label=_('Active zone'),
- doc=_('Is zone active?'),
- flags=['no_create', 'no_update'],
- attribute=True,
- ),
- Str('idnsforwarders*',
- _validate_bind_forwarder,
- cli_name='forwarder',
- label=_('Zone forwarders'),
- doc=_('Per-zone forwarders. A custom port can be specified '
- 'for each forwarder using a standard format "IP_ADDRESS port PORT"'),
- ),
- StrEnum('idnsforwardpolicy?',
- cli_name='forward_policy',
- label=_('Forward policy'),
- doc=_('Per-zone conditional forwarding policy. Set to "none" to '
- 'disable forwarding to global forwarder for this zone. In '
- 'that case, conditional zone forwarders are disregarded.'),
- values=(u'only', u'first', u'none'),
- ),
-
- )
-
- def get_dn(self, *keys, **options):
- if not dns_container_exists(self.api.Backend.ldap2):
- raise errors.NotFound(reason=_('DNS is not configured'))
-
- zone = keys[-1]
- assert isinstance(zone, DNSName)
- assert zone.is_absolute()
- zone_a = zone.ToASCII()
-
- # special case when zone is the root zone ('.')
- if zone == DNSName.root:
- return super(DNSZoneBase, self).get_dn(zone_a, **options)
-
- # try first relative name, a new zone has to be added as absolute
- # otherwise ObjectViolation is raised
- zone_a = zone_a[:-1]
- dn = super(DNSZoneBase, self).get_dn(zone_a, **options)
- try:
- self.backend.get_entry(dn, [''])
- except errors.NotFound:
- zone_a = u"%s." % zone_a
- dn = super(DNSZoneBase, self).get_dn(zone_a, **options)
-
- return dn
-
- def permission_name(self, zone):
- assert isinstance(zone, DNSName)
- return u"Manage DNS zone %s" % zone.ToASCII()
-
- def get_name_in_zone(self, zone, hostname):
- """
- Get name of a record that is to be added to a new zone. I.e. when
- we want to add record "ipa.lab.example.com" in a zone "example.com",
- this function should return "ipa.lab". Returns None when record cannot
- be added to a zone. Returns '@' when the hostname is the zone record.
- """
- assert isinstance(zone, DNSName)
- assert zone.is_absolute()
- assert isinstance(hostname, DNSName)
-
- if not hostname.is_absolute():
- return hostname
-
- if hostname.is_subdomain(zone):
- return hostname.relativize(zone)
-
- return None
-
- def _remove_permission(self, zone):
- permission_name = self.permission_name(zone)
- try:
- self.api.Command['permission_del'](permission_name, force=True)
- except errors.NotFound as e:
- if zone == DNSName.root: # special case root zone
- raise
- # compatibility, older IPA versions which allows to create zone
- # without absolute zone name
- permission_name_rel = self.permission_name(
- zone.relativize(DNSName.root)
- )
- try:
- self.api.Command['permission_del'](permission_name_rel,
- force=True)
- except errors.NotFound:
- raise e # re-raise original exception
-
- def _make_zonename_absolute(self, entry_attrs, **options):
- """
- Zone names can be relative in IPA < 4.0, make sure we always return
- absolute zone name from ldap
- """
- if options.get('raw'):
- return
-
- if "idnsname" in entry_attrs:
- entry_attrs.single_value['idnsname'] = (
- entry_attrs.single_value['idnsname'].make_absolute())
-
-
-class DNSZoneBase_add(LDAPCreate):
-
- takes_options = LDAPCreate.takes_options + (
- Flag('skip_overlap_check',
- doc=_('Force DNS zone creation even if it will overlap with '
- 'an existing zone.')
- ),
- )
-
- has_output_params = LDAPCreate.has_output_params + dnszone_output_params
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- try:
- entry = ldap.get_entry(dn)
- except errors.NotFound:
- pass
- else:
- if _check_entry_objectclass(entry, self.obj.object_class):
- self.obj.handle_duplicate_entry(*keys)
- else:
- raise errors.DuplicateEntry(
- message=_(u'Only one zone type is allowed per zone name')
- )
-
- entry_attrs['idnszoneactive'] = 'TRUE'
-
- if not options['skip_overlap_check']:
- try:
- check_zone_overlap(keys[-1])
- except ValueError as e:
- raise errors.InvocationError(e.message)
-
- return dn
-
-
-class DNSZoneBase_del(LDAPDelete):
-
- def pre_callback(self, ldap, dn, *nkeys, **options):
- assert isinstance(dn, DN)
- if not _check_DN_objectclass(ldap, dn, self.obj.object_class):
- self.obj.handle_not_found(*nkeys)
- return dn
-
- def post_callback(self, ldap, dn, *keys, **options):
- try:
- self.obj._remove_permission(keys[-1])
- except errors.NotFound:
- pass
-
- return True
-
-
-class DNSZoneBase_mod(LDAPUpdate):
- has_output_params = LDAPUpdate.has_output_params + dnszone_output_params
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj._make_zonename_absolute(entry_attrs, **options)
- return dn
-
-
-class DNSZoneBase_find(LDAPSearch):
- __doc__ = _('Search for DNS zones (SOA records).')
-
- has_output_params = LDAPSearch.has_output_params + dnszone_output_params
-
- def args_options_2_params(self, *args, **options):
- # FIXME: Check that name_from_ip is valid. This is necessary because
- # custom validation rules, including _validate_ipnet, are not
- # used when doing a search. Once we have a parameter type for
- # IP network objects, this will no longer be necessary, as the
- # parameter type will handle the validation itself (see
- # <https://fedorahosted.org/freeipa/ticket/2266>).
- if 'name_from_ip' in options:
- self.obj.params['name_from_ip'](unicode(options['name_from_ip']))
- return super(DNSZoneBase_find, self).args_options_2_params(*args, **options)
-
- def args_options_2_entry(self, *args, **options):
- if 'name_from_ip' in options:
- if 'idnsname' not in options:
- options['idnsname'] = self.obj.params['idnsname'].get_default(**options)
- del options['name_from_ip']
- search_kw = super(DNSZoneBase_find, self).args_options_2_entry(*args,
- **options)
- name = search_kw.get('idnsname')
- if name:
- search_kw['idnsname'] = [name, name.relativize(DNSName.root)]
- return search_kw
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- # Check if DNS container exists must be here for find methods
- if not dns_container_exists(self.api.Backend.ldap2):
- raise errors.NotFound(reason=_('DNS is not configured'))
- filter = _create_idn_filter(self, ldap, *args, **options)
- return (filter, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for entry_attrs in entries:
- self.obj._make_zonename_absolute(entry_attrs, **options)
- return truncated
-
-
-class DNSZoneBase_show(LDAPRetrieve):
- has_output_params = LDAPRetrieve.has_output_params + dnszone_output_params
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- if not _check_DN_objectclass(ldap, dn, self.obj.object_class):
- self.obj.handle_not_found(*keys)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj._make_zonename_absolute(entry_attrs, **options)
- return dn
-
-
-class DNSZoneBase_disable(LDAPQuery):
- has_output = output.standard_value
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- try:
- entry = ldap.get_entry(dn, ['idnszoneactive', 'objectclass'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if not _check_entry_objectclass(entry, self.obj.object_class):
- self.obj.handle_not_found(*keys)
-
- entry['idnszoneactive'] = ['FALSE']
-
- try:
- ldap.update_entry(entry)
- except errors.EmptyModlist:
- pass
-
- return dict(result=True, value=pkey_to_value(keys[-1], options))
-
-
-class DNSZoneBase_enable(LDAPQuery):
- has_output = output.standard_value
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- try:
- entry = ldap.get_entry(dn, ['idnszoneactive', 'objectclass'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if not _check_entry_objectclass(entry, self.obj.object_class):
- self.obj.handle_not_found(*keys)
-
- entry['idnszoneactive'] = ['TRUE']
-
- try:
- ldap.update_entry(entry)
- except errors.EmptyModlist:
- pass
-
- return dict(result=True, value=pkey_to_value(keys[-1], options))
-
-
-class DNSZoneBase_add_permission(LDAPQuery):
- has_output = _output_permissions
- msg_summary = _('Added system permission "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
- dn = self.obj.get_dn(*keys, **options)
-
- try:
- entry_attrs = ldap.get_entry(dn, ['objectclass'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- else:
- if not _check_entry_objectclass(entry_attrs, self.obj.object_class):
- self.obj.handle_not_found(*keys)
-
- permission_name = self.obj.permission_name(keys[-1])
-
- # compatibility with older IPA versions which allows relative zonenames
- if keys[-1] != DNSName.root: # special case root zone
- permission_name_rel = self.obj.permission_name(
- keys[-1].relativize(DNSName.root)
- )
- try:
- self.api.Object['permission'].get_dn_if_exists(
- permission_name_rel)
- except errors.NotFound:
- pass
- else:
- # permission exists without absolute domain name
- raise errors.DuplicateEntry(
- message=_('permission "%(value)s" already exists') % {
- 'value': permission_name
- }
- )
-
- permission = self.api.Command['permission_add_noaci'](permission_name,
- ipapermissiontype=u'SYSTEM'
- )['result']
-
- dnszone_ocs = entry_attrs.get('objectclass')
- if dnszone_ocs:
- for oc in dnszone_ocs:
- if oc.lower() == 'ipadnszone':
- break
- else:
- dnszone_ocs.append('ipadnszone')
-
- entry_attrs['managedby'] = [permission['dn']]
- ldap.update_entry(entry_attrs)
-
- return dict(
- result=True,
- value=pkey_to_value(permission_name, options),
- )
-
-
-class DNSZoneBase_remove_permission(LDAPQuery):
- has_output = _output_permissions
- msg_summary = _('Removed system permission "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
- dn = self.obj.get_dn(*keys, **options)
- try:
- entry = ldap.get_entry(dn, ['managedby', 'objectclass'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- else:
- if not _check_entry_objectclass(entry, self.obj.object_class):
- self.obj.handle_not_found(*keys)
-
- entry['managedby'] = None
-
- try:
- ldap.update_entry(entry)
- except errors.EmptyModlist:
- # managedBy attribute is clean, lets make sure there is also no
- # dangling DNS zone permission
- pass
-
- permission_name = self.obj.permission_name(keys[-1])
- self.obj._remove_permission(keys[-1])
-
- return dict(
- result=True,
- value=pkey_to_value(permission_name, options),
- )
-
-
-@register()
-class dnszone(DNSZoneBase):
- """
- DNS Zone, container for resource records.
- """
- object_name = _('DNS zone')
- object_name_plural = _('DNS zones')
- object_class = DNSZoneBase.object_class + ['idnsrecord', 'idnszone']
- default_attributes = DNSZoneBase.default_attributes + [
- 'idnssoamname', 'idnssoarname', 'idnssoaserial', 'idnssoarefresh',
- 'idnssoaretry', 'idnssoaexpire', 'idnssoaminimum', 'idnsallowquery',
- 'idnsallowtransfer', 'idnssecinlinesigning',
- ] + _record_attributes
- label = _('DNS Zones')
- label_singular = _('DNS Zone')
-
- takes_params = DNSZoneBase.takes_params + (
- DNSNameParam('idnssoamname?',
- cli_name='name_server',
- label=_('Authoritative nameserver'),
- doc=_('Authoritative nameserver domain name'),
- default=None, # value will be added in precallback from ldap
- ),
- DNSNameParam('idnssoarname',
- _rname_validator,
- cli_name='admin_email',
- label=_('Administrator e-mail address'),
- doc=_('Administrator e-mail address'),
- default=DNSName(u'hostmaster'),
- normalizer=normalize_zonemgr,
- autofill=True,
- ),
- Int('idnssoaserial',
- cli_name='serial',
- label=_('SOA serial'),
- doc=_('SOA record serial number'),
- minvalue=1,
- maxvalue=4294967295,
- default_from=_create_zone_serial,
- autofill=True,
- ),
- Int('idnssoarefresh',
- cli_name='refresh',
- label=_('SOA refresh'),
- doc=_('SOA record refresh time'),
- minvalue=0,
- maxvalue=2147483647,
- default=3600,
- autofill=True,
- ),
- Int('idnssoaretry',
- cli_name='retry',
- label=_('SOA retry'),
- doc=_('SOA record retry time'),
- minvalue=0,
- maxvalue=2147483647,
- default=900,
- autofill=True,
- ),
- Int('idnssoaexpire',
- cli_name='expire',
- label=_('SOA expire'),
- doc=_('SOA record expire time'),
- default=1209600,
- minvalue=0,
- maxvalue=2147483647,
- autofill=True,
- ),
- Int('idnssoaminimum',
- cli_name='minimum',
- label=_('SOA minimum'),
- doc=_('How long should negative responses be cached'),
- default=3600,
- minvalue=0,
- maxvalue=2147483647,
- autofill=True,
- ),
- Int('dnsttl?',
- cli_name='ttl',
- label=_('Time to live'),
- doc=_('Time to live for records at zone apex'),
- minvalue=0,
- maxvalue=2147483647, # see RFC 2181
- ),
- StrEnum('dnsclass?',
- # Deprecated
- cli_name='class',
- flags=['no_option'],
- values=_record_classes,
- ),
- Str('idnsupdatepolicy?',
- cli_name='update_policy',
- label=_('BIND update policy'),
- doc=_('BIND update policy'),
- default_from=lambda idnsname: default_zone_update_policy(idnsname),
- autofill=True
- ),
- Bool('idnsallowdynupdate?',
- cli_name='dynamic_update',
- label=_('Dynamic update'),
- doc=_('Allow dynamic updates.'),
- attribute=True,
- default=False,
- autofill=True
- ),
- Str('idnsallowquery?',
- _validate_bind_aci,
- normalizer=_normalize_bind_aci,
- cli_name='allow_query',
- label=_('Allow query'),
- doc=_('Semicolon separated list of IP addresses or networks which are allowed to issue queries'),
- default=u'any;', # anyone can issue queries by default
- autofill=True,
- ),
- Str('idnsallowtransfer?',
- _validate_bind_aci,
- normalizer=_normalize_bind_aci,
- cli_name='allow_transfer',
- label=_('Allow transfer'),
- doc=_('Semicolon separated list of IP addresses or networks which are allowed to transfer the zone'),
- default=u'none;', # no one can issue queries by default
- autofill=True,
- ),
- Bool('idnsallowsyncptr?',
- cli_name='allow_sync_ptr',
- label=_('Allow PTR sync'),
- doc=_('Allow synchronization of forward (A, AAAA) and reverse (PTR) records in the zone'),
- ),
- Bool('idnssecinlinesigning?',
- cli_name='dnssec',
- default=False,
- label=_('Allow in-line DNSSEC signing'),
- doc=_('Allow inline DNSSEC signing of records in the zone'),
- ),
- Str('nsec3paramrecord?',
- _validate_nsec3param_record,
- cli_name='nsec3param_rec',
- label=_('NSEC3PARAM record'),
- doc=_('NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt'),
- pattern=r'^\d+ \d+ \d+ (([0-9a-fA-F]{2})+|-)$',
- pattern_errmsg=(u'expected format: <0-255> <0-255> <0-65535> '
- 'even-length_hexadecimal_digits_or_hyphen'),
- ),
- )
- # Permissions will be apllied for forwardzones too
- # Store permissions into api.env.basedn, dns container could not exists
- managed_permissions = {
- 'System: Add DNS Entries': {
- 'non_object': True,
- 'ipapermright': {'add'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('idnsname=*', 'cn=dns', api.env.basedn),
- 'replaces': [
- '(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:add dns entries";allow (add) groupdn = "ldap:///cn=add dns entries,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- 'System: Read DNS Entries': {
- 'non_object': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('idnsname=*', 'cn=dns', api.env.basedn),
- 'ipapermdefaultattr': {
- 'objectclass',
- 'a6record', 'aaaarecord', 'afsdbrecord', 'aplrecord', 'arecord',
- 'certrecord', 'cn', 'cnamerecord', 'dhcidrecord', 'dlvrecord',
- 'dnamerecord', 'dnsclass', 'dnsttl', 'dsrecord',
- 'hinforecord', 'hiprecord', 'idnsallowdynupdate',
- 'idnsallowquery', 'idnsallowsyncptr', 'idnsallowtransfer',
- 'idnsforwarders', 'idnsforwardpolicy', 'idnsname',
- 'idnssecinlinesigning', 'idnssoaexpire', 'idnssoaminimum',
- 'idnssoamname', 'idnssoarefresh', 'idnssoaretry',
- 'idnssoarname', 'idnssoaserial', 'idnsupdatepolicy',
- 'idnszoneactive', 'ipseckeyrecord','keyrecord', 'kxrecord',
- 'locrecord', 'managedby', 'mdrecord', 'minforecord',
- 'mxrecord', 'naptrrecord', 'nsecrecord', 'nsec3paramrecord',
- 'nsrecord', 'nxtrecord', 'ptrrecord', 'rprecord', 'rrsigrecord',
- 'sigrecord', 'spfrecord', 'srvrecord', 'sshfprecord',
- 'tlsarecord', 'txtrecord', 'unknownrecord',
- },
- 'replaces_system': ['Read DNS Entries'],
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- 'System: Remove DNS Entries': {
- 'non_object': True,
- 'ipapermright': {'delete'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('idnsname=*', 'cn=dns', api.env.basedn),
- 'replaces': [
- '(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:remove dns entries";allow (delete) groupdn = "ldap:///cn=remove dns entries,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- 'System: Update DNS Entries': {
- 'non_object': True,
- 'ipapermright': {'write'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('idnsname=*', 'cn=dns', api.env.basedn),
- 'ipapermdefaultattr': {
- 'a6record', 'aaaarecord', 'afsdbrecord', 'aplrecord', 'arecord',
- 'certrecord', 'cn', 'cnamerecord', 'dhcidrecord', 'dlvrecord',
- 'dnamerecord', 'dnsclass', 'dnsttl', 'dsrecord',
- 'hinforecord', 'hiprecord', 'idnsallowdynupdate',
- 'idnsallowquery', 'idnsallowsyncptr', 'idnsallowtransfer',
- 'idnsforwarders', 'idnsforwardpolicy', 'idnsname',
- 'idnssecinlinesigning', 'idnssoaexpire', 'idnssoaminimum',
- 'idnssoamname', 'idnssoarefresh', 'idnssoaretry',
- 'idnssoarname', 'idnssoaserial', 'idnsupdatepolicy',
- 'idnszoneactive', 'ipseckeyrecord','keyrecord', 'kxrecord',
- 'locrecord', 'managedby', 'mdrecord', 'minforecord',
- 'mxrecord', 'naptrrecord', 'nsecrecord', 'nsec3paramrecord',
- 'nsrecord', 'nxtrecord', 'ptrrecord', 'rprecord', 'rrsigrecord',
- 'sigrecord', 'spfrecord', 'srvrecord', 'sshfprecord',
- 'tlsarecord', 'txtrecord', 'unknownrecord',
- },
- 'replaces': [
- '(targetattr = "idnsname || cn || idnsallowdynupdate || dnsttl || dnsclass || arecord || aaaarecord || a6record || nsrecord || cnamerecord || ptrrecord || srvrecord || txtrecord || mxrecord || mdrecord || hinforecord || minforecord || afsdbrecord || sigrecord || keyrecord || locrecord || nxtrecord || naptrrecord || kxrecord || certrecord || dnamerecord || dsrecord || sshfprecord || rrsigrecord || nsecrecord || idnsname || idnszoneactive || idnssoamname || idnssoarname || idnssoaserial || idnssoarefresh || idnssoaretry || idnssoaexpire || idnssoaminimum || idnsupdatepolicy")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:update dns entries";allow (write) groupdn = "ldap:///cn=update dns entries,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetattr = "idnsname || cn || idnsallowdynupdate || dnsttl || dnsclass || arecord || aaaarecord || a6record || nsrecord || cnamerecord || ptrrecord || srvrecord || txtrecord || mxrecord || mdrecord || hinforecord || minforecord || afsdbrecord || sigrecord || keyrecord || locrecord || nxtrecord || naptrrecord || kxrecord || certrecord || dnamerecord || dsrecord || sshfprecord || rrsigrecord || nsecrecord || idnsname || idnszoneactive || idnssoamname || idnssoarname || idnssoaserial || idnssoarefresh || idnssoaretry || idnssoaexpire || idnssoaminimum || idnsupdatepolicy || idnsallowquery || idnsallowtransfer || idnsallowsyncptr || idnsforwardpolicy || idnsforwarders")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:update dns entries";allow (write) groupdn = "ldap:///cn=update dns entries,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetattr = "idnsname || cn || idnsallowdynupdate || dnsttl || dnsclass || arecord || aaaarecord || a6record || nsrecord || cnamerecord || ptrrecord || srvrecord || txtrecord || mxrecord || mdrecord || hinforecord || minforecord || afsdbrecord || sigrecord || keyrecord || locrecord || nxtrecord || naptrrecord || kxrecord || certrecord || dnamerecord || dsrecord || sshfprecord || rrsigrecord || nsecrecord || idnsname || idnszoneactive || idnssoamname || idnssoarname || idnssoaserial || idnssoarefresh || idnssoaretry || idnssoaexpire || idnssoaminimum || idnsupdatepolicy || idnsallowquery || idnsallowtransfer || idnsallowsyncptr || idnsforwardpolicy || idnsforwarders || managedby")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX")(version 3.0;acl "permission:update dns entries";allow (write) groupdn = "ldap:///cn=update dns entries,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- 'System: Read DNSSEC metadata': {
- 'non_object': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=dns', api.env.basedn),
- 'ipapermtargetfilter': ['(objectclass=idnsSecKey)'],
- 'ipapermdefaultattr': {
- 'idnsSecAlgorithm', 'idnsSecKeyCreated', 'idnsSecKeyPublish',
- 'idnsSecKeyActivate', 'idnsSecKeyInactive', 'idnsSecKeyDelete',
- 'idnsSecKeyZone', 'idnsSecKeyRevoke', 'idnsSecKeySep',
- 'idnsSecKeyRef', 'cn', 'objectclass',
- },
- 'default_privileges': {'DNS Administrators'},
- },
- 'System: Manage DNSSEC metadata': {
- 'non_object': True,
- 'ipapermright': {'all'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=dns', api.env.basedn),
- 'ipapermtargetfilter': ['(objectclass=idnsSecKey)'],
- 'ipapermdefaultattr': {
- 'idnsSecAlgorithm', 'idnsSecKeyCreated', 'idnsSecKeyPublish',
- 'idnsSecKeyActivate', 'idnsSecKeyInactive', 'idnsSecKeyDelete',
- 'idnsSecKeyZone', 'idnsSecKeyRevoke', 'idnsSecKeySep',
- 'idnsSecKeyRef', 'cn', 'objectclass',
- },
- 'default_privileges': {'DNS Servers'},
- },
- 'System: Manage DNSSEC keys': {
- 'non_object': True,
- 'ipapermright': {'all'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=keys', 'cn=sec', 'cn=dns', api.env.basedn),
- 'ipapermdefaultattr': {
- 'ipaPublicKey', 'ipaPrivateKey', 'ipaSecretKey',
- 'ipaWrappingMech','ipaWrappingKey',
- 'ipaSecretKeyRef', 'ipk11Private', 'ipk11Modifiable', 'ipk11Label',
- 'ipk11Copyable', 'ipk11Destroyable', 'ipk11Trusted',
- 'ipk11CheckValue', 'ipk11StartDate', 'ipk11EndDate',
- 'ipk11UniqueId', 'ipk11PublicKeyInfo', 'ipk11Distrusted',
- 'ipk11Subject', 'ipk11Id', 'ipk11Local', 'ipk11KeyType',
- 'ipk11Derive', 'ipk11KeyGenMechanism', 'ipk11AllowedMechanisms',
- 'ipk11Encrypt', 'ipk11Verify', 'ipk11VerifyRecover', 'ipk11Wrap',
- 'ipk11WrapTemplate', 'ipk11Sensitive', 'ipk11Decrypt',
- 'ipk11Sign', 'ipk11SignRecover', 'ipk11Unwrap',
- 'ipk11Extractable', 'ipk11AlwaysSensitive',
- 'ipk11NeverExtractable', 'ipk11WrapWithTrusted',
- 'ipk11UnwrapTemplate', 'ipk11AlwaysAuthenticate',
- 'objectclass',
- },
- 'default_privileges': {'DNS Servers'},
- },
- }
-
- def _rr_zone_postprocess(self, record, **options):
- #Decode IDN ACE form to Unicode, raw records are passed directly from LDAP
- if options.get('raw', False):
- return
- _records_idn_postprocess(record, **options)
-
- def _warning_forwarding(self, result, **options):
- if ('idnsforwarders' in result['result']):
- messages.add_message(options.get('version', VERSION_WITHOUT_CAPABILITIES),
- result, messages.ForwardersWarning())
-
- def _warning_name_server_option(self, result, context, **options):
- if getattr(context, 'show_warning_nameserver_option', False):
- messages.add_message(
- options['version'],
- result, messages.OptionSemanticChangedWarning(
- label=_(u"setting Authoritative nameserver"),
- current_behavior=_(u"It is used only for setting the "
- u"SOA MNAME attribute."),
- hint=_(u"NS record(s) can be edited in zone apex - '@'. ")
- )
- )
-
- def _warning_fw_zone_is_not_effective(self, result, *keys, **options):
- """
- Warning if any operation with zone causes, a child forward zone is
- not effective
- """
- zone = keys[-1]
- affected_fw_zones, truncated = _find_subtree_forward_zones_ldap(
- self.api, zone, child_zones_only=True)
- if not affected_fw_zones:
- return
-
- for fwzone in affected_fw_zones:
- _add_warning_fw_zone_is_not_effective(self.api, result, fwzone,
- options['version'])
-
- def _warning_dnssec_master_is_not_installed(self, result, **options):
- dnssec_enabled = result['result'].get("idnssecinlinesigning", False)
- if dnssec_enabled and not dnssec_installed(self.api.Backend.ldap2):
- messages.add_message(
- options['version'],
- result,
- messages.DNSSECMasterNotInstalled()
- )
-
-
-@register()
-class dnszone_add(DNSZoneBase_add):
- __doc__ = _('Create new DNS zone (SOA record).')
-
- takes_options = DNSZoneBase_add.takes_options + (
- Flag('force',
- doc=_('Force DNS zone creation even if nameserver is not '
- 'resolvable. (Deprecated)'),
- ),
-
- Flag('skip_nameserver_check',
- doc=_('Force DNS zone creation even if nameserver is not '
- 'resolvable.'),
- ),
-
- # Deprecated
- # ip-address option is not used anymore, we have to keep it
- # due to compability with clients older than 4.1
- Str('ip_address?',
- flags=['no_option', ]
- ),
- )
-
- def _warning_deprecated_option(self, result, **options):
- if 'ip_address' in options:
- messages.add_message(
- options['version'],
- result,
- messages.OptionDeprecatedWarning(
- option='ip-address',
- additional_info=u"Value will be ignored.")
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- if options.get('force'):
- options['skip_nameserver_check'] = True
-
- dn = super(dnszone_add, self).pre_callback(
- ldap, dn, entry_attrs, attrs_list, *keys, **options)
-
- nameservers = [normalize_zone(x) for x in
- self.api.Object.dnsrecord.get_dns_masters()]
- server = normalize_zone(api.env.host)
- zone = keys[-1]
-
- if entry_attrs.get('idnssoamname'):
- if zone.is_reverse() and not entry_attrs['idnssoamname'].is_absolute():
- raise errors.ValidationError(
- name='name-server',
- error=_("Nameserver for reverse zone cannot be a relative DNS name"))
-
- # verify if user specified server is resolvable
- if not options['skip_nameserver_check']:
- check_ns_rec_resolvable(keys[0], entry_attrs['idnssoamname'],
- self.log)
- # show warning about --name-server option
- context.show_warning_nameserver_option = True
- else:
- # user didn't specify SOA mname
- if server in nameservers:
- # current ipa server is authoritative nameserver in SOA record
- entry_attrs['idnssoamname'] = [server]
- else:
- # a first DNS capable server is authoritative nameserver in SOA record
- entry_attrs['idnssoamname'] = [nameservers[0]]
-
- # all ipa DNS servers should be in NS zone record (as absolute domain name)
- entry_attrs['nsrecord'] = nameservers
-
- return dn
-
- def execute(self, *keys, **options):
- result = super(dnszone_add, self).execute(*keys, **options)
- self._warning_deprecated_option(result, **options)
- self.obj._warning_forwarding(result, **options)
- self.obj._warning_name_server_option(result, context, **options)
- self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
- self.obj._warning_dnssec_master_is_not_installed(result, **options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- # Add entry to realmdomains
- # except for our own domain, forward zones, reverse zones and root zone
- zone = keys[0]
-
- if (zone != DNSName(api.env.domain).make_absolute() and
- not options.get('idnsforwarders') and
- not zone.is_reverse() and
- zone != DNSName.root):
- try:
- self.api.Command['realmdomains_mod'](add_domain=unicode(zone),
- force=True)
- except (errors.EmptyModlist, errors.ValidationError):
- pass
-
- self.obj._rr_zone_postprocess(entry_attrs, **options)
- return dn
-
-
-
-@register()
-class dnszone_del(DNSZoneBase_del):
- __doc__ = _('Delete DNS zone (SOA record).')
-
- msg_summary = _('Deleted DNS zone "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(dnszone_del, self).execute(*keys, **options)
- nkeys = keys[-1] # we can delete more zones
- for key in nkeys:
- self.obj._warning_fw_zone_is_not_effective(result, key, **options)
- return result
-
- def post_callback(self, ldap, dn, *keys, **options):
- super(dnszone_del, self).post_callback(ldap, dn, *keys, **options)
-
- # Delete entry from realmdomains
- # except for our own domain, reverse zone, and root zone
- zone = keys[0].make_absolute()
-
- if (zone != DNSName(api.env.domain).make_absolute() and
- not zone.is_reverse() and zone != DNSName.root
- ):
- try:
- self.api.Command['realmdomains_mod'](
- del_domain=unicode(zone), force=True)
- except (errors.AttrValueNotFound, errors.ValidationError):
- pass
-
- return True
-
-
-
-@register()
-class dnszone_mod(DNSZoneBase_mod):
- __doc__ = _('Modify DNS zone (SOA record).')
-
- takes_options = DNSZoneBase_mod.takes_options + (
- Flag('force',
- label=_('Force'),
- doc=_('Force nameserver change even if nameserver not in DNS'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- if not _check_DN_objectclass(ldap, dn, self.obj.object_class):
- self.obj.handle_not_found(*keys)
- if 'idnssoamname' in entry_attrs:
- nameserver = entry_attrs['idnssoamname']
- if nameserver:
- if not nameserver.is_empty() and not options['force']:
- check_ns_rec_resolvable(keys[0], nameserver, self.log)
- context.show_warning_nameserver_option = True
- else:
- # empty value, this option is required by ldap
- raise errors.ValidationError(
- name='name_server',
- error=_(u"is required"))
-
- return dn
-
- def execute(self, *keys, **options):
- result = super(dnszone_mod, self).execute(*keys, **options)
- self.obj._warning_forwarding(result, **options)
- self.obj._warning_name_server_option(result, context, **options)
- self.obj._warning_dnssec_master_is_not_installed(result, **options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- dn = super(dnszone_mod, self).post_callback(ldap, dn, entry_attrs,
- *keys, **options)
- self.obj._rr_zone_postprocess(entry_attrs, **options)
- return dn
-
-
-@register()
-class dnszone_find(DNSZoneBase_find):
- __doc__ = _('Search for DNS zones (SOA records).')
-
- takes_options = DNSZoneBase_find.takes_options + (
- Flag('forward_only',
- label=_('Forward zones only'),
- cli_name='forward_only',
- doc=_('Search for forward zones only'),
- ),
- )
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
-
- filter, base, dn = super(dnszone_find, self).pre_callback(ldap, filter,
- attrs_list, base_dn, scope, *args, **options)
-
- if options.get('forward_only', False):
- search_kw = {}
- search_kw['idnsname'] = [revzone.ToASCII() for revzone in
- REVERSE_DNS_ZONES.keys()]
- rev_zone_filter = ldap.make_filter(search_kw,
- rules=ldap.MATCH_NONE,
- exact=False,
- trailing_wildcard=False)
- filter = ldap.combine_filters((rev_zone_filter, filter),
- rules=ldap.MATCH_ALL)
-
- return (filter, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- truncated = super(dnszone_find, self).post_callback(ldap, entries,
- truncated, *args,
- **options)
- for entry_attrs in entries:
- self.obj._rr_zone_postprocess(entry_attrs, **options)
- return truncated
-
-
-
-@register()
-class dnszone_show(DNSZoneBase_show):
- __doc__ = _('Display information about a DNS zone (SOA record).')
-
- def execute(self, *keys, **options):
- result = super(dnszone_show, self).execute(*keys, **options)
- self.obj._warning_forwarding(result, **options)
- self.obj._warning_dnssec_master_is_not_installed(result, **options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- dn = super(dnszone_show, self).post_callback(ldap, dn, entry_attrs,
- *keys, **options)
- self.obj._rr_zone_postprocess(entry_attrs, **options)
- return dn
-
-
-
-@register()
-class dnszone_disable(DNSZoneBase_disable):
- __doc__ = _('Disable DNS Zone.')
- msg_summary = _('Disabled DNS zone "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(dnszone_disable, self).execute(*keys, **options)
- self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
- return result
-
-
-@register()
-class dnszone_enable(DNSZoneBase_enable):
- __doc__ = _('Enable DNS Zone.')
- msg_summary = _('Enabled DNS zone "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(dnszone_enable, self).execute(*keys, **options)
- self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
- return result
-
-
-@register()
-class dnszone_add_permission(DNSZoneBase_add_permission):
- __doc__ = _('Add a permission for per-zone access delegation.')
-
-
-@register()
-class dnszone_remove_permission(DNSZoneBase_remove_permission):
- __doc__ = _('Remove a permission for per-zone access delegation.')
-
-
-@register()
-class dnsrecord(LDAPObject):
- """
- DNS record.
- """
- parent_object = 'dnszone'
- container_dn = api.env.container_dns
- object_name = _('DNS resource record')
- object_name_plural = _('DNS resource records')
- object_class = ['top', 'idnsrecord']
- permission_filter_objectclasses = ['idnsrecord']
- default_attributes = ['idnsname'] + _record_attributes
- rdn_is_primary_key = True
-
- label = _('DNS Resource Records')
- label_singular = _('DNS Resource Record')
-
- takes_params = (
- DNSNameParam('idnsname',
- cli_name='name',
- label=_('Record name'),
- doc=_('Record name'),
- primary_key=True,
- ),
- Int('dnsttl?',
- cli_name='ttl',
- label=_('Time to live'),
- doc=_('Time to live'),
- ),
- StrEnum('dnsclass?',
- # Deprecated
- cli_name='class',
- flags=['no_option'],
- values=_record_classes,
- ),
- ) + _dns_record_options
-
- structured_flag = Flag('structured',
- label=_('Structured'),
- doc=_('Parse all raw DNS records and return them in a structured way'),
- )
-
- def _dsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- dsrecords = entry_attrs.get('dsrecord')
- if dsrecords and self.is_pkey_zone_record(*keys):
- raise errors.ValidationError(
- name='dsrecord',
- error=unicode(_('DS record must not be in zone apex (RFC 4035 section 2.4)')))
-
- def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- nsrecords = entry_attrs.get('nsrecord')
- if options.get('force', False) or nsrecords is None:
- return
- for nsrecord in nsrecords:
- check_ns_rec_resolvable(keys[0], DNSName(nsrecord), self.log)
-
- def _idnsname_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if keys[-1].is_absolute():
- if keys[-1].is_subdomain(keys[-2]):
- entry_attrs['idnsname'] = [keys[-1].relativize(keys[-2])]
- elif not self.is_pkey_zone_record(*keys):
- raise errors.ValidationError(name='idnsname',
- error=unicode(_('out-of-zone data: record name must '
- 'be a subdomain of the zone or a '
- 'relative name')))
- # dissallowed wildcard (RFC 4592 section 4)
- no_wildcard_rtypes = ['DNAME', 'DS', 'NS']
- if (keys[-1].is_wild() and
- any(entry_attrs.get(record_name_format % r.lower())
- for r in no_wildcard_rtypes)
- ):
- raise errors.ValidationError(
- name='idnsname',
- error=(_('owner of %(types)s records '
- 'should not be a wildcard domain name (RFC 4592 section 4)') %
- {'types': ', '.join(no_wildcard_rtypes)}
- )
- )
-
- def _ptrrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- ptrrecords = entry_attrs.get('ptrrecord')
- if ptrrecords is None:
- return
-
- zone = keys[-2]
- if self.is_pkey_zone_record(*keys):
- addr = _dns_zone_record
- else:
- addr = keys[-1]
-
- zone_len = 0
- for valid_zone in REVERSE_DNS_ZONES:
- if zone.is_subdomain(valid_zone):
- zone = zone.relativize(valid_zone)
- zone_name = valid_zone
- zone_len = REVERSE_DNS_ZONES[valid_zone]
-
- if not zone_len:
- allowed_zones = ', '.join([unicode(revzone) for revzone in
- REVERSE_DNS_ZONES.keys()])
- raise errors.ValidationError(name='ptrrecord',
- error=unicode(_('Reverse zone for PTR record should be a sub-zone of one the following fully qualified domains: %s') % allowed_zones))
-
- addr_len = len(addr.labels)
-
- # Classless zones (0/25.0.0.10.in-addr.arpa.) -> skip check
- # zone has to be checked without reverse domain suffix (in-addr.arpa.)
- for sign in ('/', '-'):
- for name in (zone, addr):
- for label in name.labels:
- if sign in label:
- return
-
- ip_addr_comp_count = addr_len + len(zone.labels)
- if ip_addr_comp_count != zone_len:
- raise errors.ValidationError(name='ptrrecord',
- error=unicode(_('Reverse zone %(name)s requires exactly '
- '%(count)d IP address components, '
- '%(user_count)d given')
- % dict(name=zone_name,
- count=zone_len,
- user_count=ip_addr_comp_count)))
-
- def run_precallback_validators(self, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- ldap = self.api.Backend.ldap2
-
- for rtype in entry_attrs.keys():
- rtype_cb = getattr(self, '_%s_pre_callback' % rtype, None)
- if rtype_cb:
- rtype_cb(ldap, dn, entry_attrs, *keys, **options)
-
- def is_pkey_zone_record(self, *keys):
- assert isinstance(keys[-1], DNSName)
- assert isinstance(keys[-2], DNSName)
- idnsname = keys[-1]
- zonename = keys[-2]
- if idnsname.is_empty() or idnsname == zonename:
- return True
- return False
-
- def check_zone(self, zone, **options):
- """
- Check if zone exists and if is master zone
- """
- parent_object = self.api.Object[self.parent_object]
- dn = parent_object.get_dn(zone, **options)
- ldap = self.api.Backend.ldap2
- try:
- entry = ldap.get_entry(dn, ['objectclass'])
- except errors.NotFound:
- parent_object.handle_not_found(zone)
- else:
- # only master zones can contain records
- if 'idnszone' not in [x.lower() for x in entry.get('objectclass', [])]:
- raise errors.ValidationError(
- name='dnszoneidnsname',
- error=_(u'only master zones can contain records')
- )
- return dn
-
-
- def get_dn(self, *keys, **options):
- if not dns_container_exists(self.api.Backend.ldap2):
- raise errors.NotFound(reason=_('DNS is not configured'))
-
- dn = self.check_zone(keys[-2], **options)
-
- if self.is_pkey_zone_record(*keys):
- return dn
-
- #Make RR name relative if possible
- relative_name = keys[-1].relativize(keys[-2]).ToASCII()
- keys = keys[:-1] + (relative_name,)
- return super(dnsrecord, self).get_dn(*keys, **options)
-
- def attr_to_cli(self, attr):
- cliname = get_record_rrtype(attr)
- if not cliname:
- cliname = attr
- return cliname
-
- def get_dns_masters(self):
- ldap = self.api.Backend.ldap2
- base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.api.env.basedn)
- ldap_filter = '(&(objectClass=ipaConfigObject)(cn=DNS))'
- dns_masters = []
-
- try:
- entries = ldap.find_entries(filter=ldap_filter, base_dn=base_dn)[0]
-
- for entry in entries:
- try:
- master = entry.dn[1]['cn']
- dns_masters.append(master)
- except (IndexError, KeyError):
- pass
- except errors.NotFound:
- return []
-
- return dns_masters
-
- def get_record_entry_attrs(self, entry_attrs):
- entry_attrs = entry_attrs.copy()
- for attr in entry_attrs.keys():
- if attr not in self.params or self.params[attr].primary_key:
- del entry_attrs[attr]
- return entry_attrs
-
- def postprocess_record(self, record, **options):
- if options.get('structured', False):
- for attr in record.keys():
- # attributes in LDAPEntry may not be normalized
- attr = attr.lower()
- try:
- param = self.params[attr]
- except KeyError:
- continue
-
- if not isinstance(param, DNSRecord):
- continue
- parts_params = param.get_parts()
-
- for dnsvalue in record[attr]:
- dnsentry = {
- u'dnstype' : unicode(param.rrtype),
- u'dnsdata' : dnsvalue
- }
- values = param._get_part_values(dnsvalue)
- if values is None:
- continue
- for val_id, val in enumerate(values):
- if val is not None:
- #decode IDN
- if isinstance(parts_params[val_id], DNSNameParam):
- dnsentry[parts_params[val_id].name] = \
- _dns_name_to_string(val,
- options.get('raw', False))
- else:
- dnsentry[parts_params[val_id].name] = val
- record.setdefault('dnsrecords', []).append(dnsentry)
- del record[attr]
-
- elif not options.get('raw', False):
- #Decode IDN ACE form to Unicode, raw records are passed directly from LDAP
- _records_idn_postprocess(record, **options)
-
- def updated_rrattrs(self, old_entry, entry_attrs):
- """Returns updated RR attributes
- """
- rrattrs = {}
- if old_entry is not None:
- old_rrattrs = dict((key, value) for key, value in old_entry.items()
- if key in self.params and
- isinstance(self.params[key], DNSRecord))
- rrattrs.update(old_rrattrs)
- new_rrattrs = dict((key, value) for key, value in entry_attrs.items()
- if key in self.params and
- isinstance(self.params[key], DNSRecord))
- rrattrs.update(new_rrattrs)
- return rrattrs
-
- def check_record_type_collisions(self, keys, rrattrs):
- # Test that only allowed combination of record types was created
-
- # CNAME record validation
- cnames = rrattrs.get('cnamerecord')
- if cnames is not None:
- if len(cnames) > 1:
- raise errors.ValidationError(name='cnamerecord',
- error=_('only one CNAME record is allowed per name '
- '(RFC 2136, section 1.1.5)'))
- if any(rrvalue is not None
- and rrattr != 'cnamerecord'
- for rrattr, rrvalue in rrattrs.items()):
- raise errors.ValidationError(name='cnamerecord',
- error=_('CNAME record is not allowed to coexist '
- 'with any other record (RFC 1034, section 3.6.2)'))
-
- # DNAME record validation
- dnames = rrattrs.get('dnamerecord')
- if dnames is not None:
- if len(dnames) > 1:
- raise errors.ValidationError(name='dnamerecord',
- error=_('only one DNAME record is allowed per name '
- '(RFC 6672, section 2.4)'))
- # DNAME must not coexist with CNAME, but this is already checked earlier
-
- # NS record validation
- # NS record can coexist only with A, AAAA, DS, and other NS records (except zone apex)
- # RFC 2181 section 6.1,
- allowed_records = ['AAAA', 'A', 'DS', 'NS']
- nsrecords = rrattrs.get('nsrecord')
- if nsrecords and not self.is_pkey_zone_record(*keys):
- for r_type in _record_types:
- if (r_type not in allowed_records
- and rrattrs.get(record_name_format % r_type.lower())
- ):
- raise errors.ValidationError(
- name='nsrecord',
- error=_('NS record is not allowed to coexist with an '
- '%(type)s record except when located in a '
- 'zone root record (RFC 2181, section 6.1)') %
- {'type': r_type})
-
- def check_record_type_dependencies(self, keys, rrattrs):
- # Test that all record type dependencies are satisfied
-
- # DS record validation
- # DS record requires to coexists with NS record
- dsrecords = rrattrs.get('dsrecord')
- nsrecords = rrattrs.get('nsrecord')
- # DS record cannot be in zone apex, checked in pre-callback validators
- if dsrecords and not nsrecords:
- raise errors.ValidationError(
- name='dsrecord',
- error=_('DS record requires to coexist with an '
- 'NS record (RFC 4592 section 4.6, RFC 4035 section 2.4)'))
-
- def _entry2rrsets(self, entry_attrs, dns_name, dns_domain):
- '''Convert entry_attrs to a dictionary {rdtype: rrset}.
-
- :returns:
- None if entry_attrs is None
- {rdtype: None} if RRset of given type is empty
- {rdtype: RRset} if RRset of given type is non-empty
- '''
- ldap_rrsets = {}
-
- if not entry_attrs:
- # all records were deleted => name should not exist in DNS
- return None
-
- for attr, value in entry_attrs.items():
- rrtype = get_record_rrtype(attr)
- if not rrtype:
- continue
-
- rdtype = dns.rdatatype.from_text(rrtype)
- if not value:
- ldap_rrsets[rdtype] = None # RRset is empty
- continue
-
- try:
- # TTL here can be arbitrary value because it is ignored
- # during comparison
- ldap_rrset = dns.rrset.from_text(
- dns_name, 86400, dns.rdataclass.IN, rdtype,
- *[str(v) for v in value])
-
- # make sure that all names are absolute so RRset
- # comparison will work
- for ldap_rr in ldap_rrset:
- ldap_rr.choose_relativity(origin=dns_domain,
- relativize=False)
- ldap_rrsets[rdtype] = ldap_rrset
-
- except dns.exception.SyntaxError as e:
- self.log.error('DNS syntax error: %s %s %s: %s', dns_name,
- dns.rdatatype.to_text(rdtype), value, e)
- raise
-
- return ldap_rrsets
-
- def wait_for_modified_attr(self, ldap_rrset, rdtype, dns_name):
- '''Wait until DNS resolver returns up-to-date answer for given RRset
- or until the maximum number of attempts is reached.
- Number of attempts is controlled by self.api.env['wait_for_dns'].
-
- :param ldap_rrset:
- None if given rdtype should not exist or
- dns.rrset.RRset to match against data in DNS.
- :param dns_name: FQDN to query
- :type dns_name: dns.name.Name
- :return: None if data in DNS and LDAP match
- :raises errors.DNSDataMismatch: if data in DNS and LDAP doesn't match
- :raises dns.exception.DNSException: if DNS resolution failed
- '''
- resolver = dns.resolver.Resolver()
- resolver.set_flags(0) # disable recursion (for NS RR checks)
- max_attempts = int(self.api.env['wait_for_dns'])
- warn_attempts = max_attempts // 2
- period = 1 # second
- attempt = 0
- log_fn = self.log.debug
- log_fn('querying DNS server: expecting answer {%s}', ldap_rrset)
- wait_template = 'waiting for DNS answer {%s}: got {%s} (attempt %s); '\
- 'waiting %s seconds before next try'
-
- while attempt < max_attempts:
- if attempt >= warn_attempts:
- log_fn = self.log.warning
- attempt += 1
- try:
- dns_answer = resolver.query(dns_name, rdtype,
- dns.rdataclass.IN,
- raise_on_no_answer=False)
- dns_rrset = None
- if rdtype == _NS:
- # NS records can be in Authority section (sometimes)
- dns_rrset = dns_answer.response.get_rrset(
- dns_answer.response.authority, dns_name, _IN, rdtype)
-
- if not dns_rrset:
- # Look for NS and other data in Answer section
- dns_rrset = dns_answer.rrset
-
- if dns_rrset == ldap_rrset:
- log_fn('DNS answer matches expectations (attempt %s)',
- attempt)
- return
-
- log_msg = wait_template % (ldap_rrset, dns_answer.response,
- attempt, period)
-
- except (dns.resolver.NXDOMAIN,
- dns.resolver.YXDOMAIN,
- dns.resolver.NoNameservers,
- dns.resolver.Timeout) as e:
- if attempt >= max_attempts:
- raise
- else:
- log_msg = wait_template % (ldap_rrset, type(e), attempt,
- period)
-
- log_fn(log_msg)
- time.sleep(period)
-
- # Maximum number of attempts was reached
- else:
- raise errors.DNSDataMismatch(expected=ldap_rrset, got=dns_rrset)
-
- def wait_for_modified_attrs(self, entry_attrs, dns_name, dns_domain):
- '''Wait until DNS resolver returns up-to-date answer for given entry
- or until the maximum number of attempts is reached.
-
- :param entry_attrs:
- None if the entry was deleted from LDAP or
- LDAPEntry instance containing at least all modified attributes.
- :param dns_name: FQDN
- :type dns_name: dns.name.Name
- :raises errors.DNSDataMismatch: if data in DNS and LDAP doesn't match
- '''
-
- # represent data in LDAP as dictionary rdtype => rrset
- ldap_rrsets = self._entry2rrsets(entry_attrs, dns_name, dns_domain)
- nxdomain = ldap_rrsets is None
- if nxdomain:
- # name should not exist => ask for A record and check result
- ldap_rrsets = {dns.rdatatype.from_text('A'): None}
-
- for rdtype, ldap_rrset in ldap_rrsets.items():
- try:
- self.wait_for_modified_attr(ldap_rrset, rdtype, dns_name)
-
- except dns.resolver.NXDOMAIN as e:
- if nxdomain:
- continue
- else:
- e = errors.DNSDataMismatch(expected=ldap_rrset,
- got="NXDOMAIN")
- self.log.error(e)
- raise e
-
- except dns.resolver.NoNameservers as e:
- # Do not raise exception if we have got SERVFAILs.
- # Maybe the user has created an invalid zone intentionally.
- self.log.warning('waiting for DNS answer {%s}: got {%s}; '
- 'ignoring', ldap_rrset, type(e))
- continue
-
- except dns.exception.DNSException as e:
- err_desc = str(type(e))
- err_str = str(e)
- if err_str:
- err_desc += ": %s" % err_str
- e = errors.DNSDataMismatch(expected=ldap_rrset, got=err_desc)
- self.log.error(e)
- raise e
-
- def wait_for_modified_entries(self, entries):
- '''Call wait_for_modified_attrs for all entries in given dict.
-
- :param entries:
- Dict {(dns_domain, dns_name): entry_for_wait_for_modified_attrs}
- '''
- for entry_name, entry in entries.items():
- dns_domain = entry_name[0]
- dns_name = entry_name[1].derelativize(dns_domain)
- self.wait_for_modified_attrs(entry, dns_name, dns_domain)
-
- def warning_if_ns_change_cause_fwzone_ineffective(self, result, *keys,
- **options):
- """Detect if NS record change can make forward zones ineffective due
- missing delegation. Run after parent's execute method.
- """
- record_name_absolute = keys[-1]
- zone = keys[-2]
-
- if not record_name_absolute.is_absolute():
- record_name_absolute = record_name_absolute.derelativize(zone)
-
- affected_fw_zones, truncated = _find_subtree_forward_zones_ldap(
- self.api, record_name_absolute)
- if not affected_fw_zones:
- return
-
- for fwzone in affected_fw_zones:
- _add_warning_fw_zone_is_not_effective(self.api, result, fwzone,
- options['version'])
-
- def warning_suspicious_relative_name(self, result, *keys, **options):
- """Detect if zone name is suffix of relative record name and warn.
-
- Zone name: test.zone.
- Relative name: record.test.zone
- """
- record_name = keys[-1]
- zone = keys[-2]
- if not record_name.is_absolute() and record_name.is_subdomain(
- zone.relativize(DNSName.root)):
- messages.add_message(
- options['version'],
- result,
- messages.DNSSuspiciousRelativeName(record=record_name,
- zone=zone,
- fqdn=record_name + zone)
- )
-
-
-@register()
-class dnsrecord_split_parts(Command):
- NO_CLI = True
-
- takes_args = (
- Str('name'),
- Str('value'),
- )
-
- def execute(self, name, value, *args, **options):
- result = self.api.Object.dnsrecord.params[name]._get_part_values(value)
- return dict(result=result)
-
-
-@register()
-class dnsrecord_add(LDAPCreate):
- __doc__ = _('Add new DNS resource record.')
-
- no_option_msg = 'No options to add a specific record provided.\n' \
- "Command help may be consulted for all supported record types."
- takes_options = LDAPCreate.takes_options + (
- Flag('force',
- label=_('Force'),
- flags=['no_option', 'no_output'],
- doc=_('force NS record creation even if its hostname is not in DNS'),
- ),
- dnsrecord.structured_flag,
- )
-
- def args_options_2_entry(self, *keys, **options):
- has_cli_options(self, options, self.no_option_msg)
- return super(dnsrecord_add, self).args_options_2_entry(*keys, **options)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- precallback_attrs = []
- processed_attrs = []
- for option in options:
- try:
- param = self.params[option]
- except KeyError:
- continue
-
- rrparam = get_rrparam_from_part(self, option)
- if rrparam is None:
- continue
-
- if 'dnsrecord_part' in param.flags:
- if rrparam.name in processed_attrs:
- # this record was already entered
- continue
- if rrparam.name in entry_attrs:
- # this record is entered both via parts and raw records
- raise errors.ValidationError(name=param.cli_name or param.name,
- error=_('Raw value of a DNS record was already set by "%(name)s" option') \
- % dict(name=rrparam.cli_name or rrparam.name))
-
- parts = rrparam.get_parts_from_kw(options)
- dnsvalue = [rrparam._convert_scalar(parts)]
- entry_attrs[rrparam.name] = dnsvalue
- processed_attrs.append(rrparam.name)
- continue
-
- if 'dnsrecord_extra' in param.flags:
- # do not run precallback for unset flags
- if isinstance(param, Flag) and not options[option]:
- continue
- # extra option is passed, run per-type pre_callback for given RR type
- precallback_attrs.append(rrparam.name)
-
- # Run pre_callback validators
- self.obj.run_precallback_validators(dn, entry_attrs, *keys, **options)
-
- # run precallback also for all new RR type attributes in entry_attrs
- for attr in entry_attrs.keys():
- try:
- param = self.params[attr]
- except KeyError:
- continue
-
- if not isinstance(param, DNSRecord):
- continue
- precallback_attrs.append(attr)
-
- precallback_attrs = list(set(precallback_attrs))
-
- for attr in precallback_attrs:
- # run per-type
- try:
- param = self.params[attr]
- except KeyError:
- continue
- param.dnsrecord_add_pre_callback(ldap, dn, entry_attrs, attrs_list, *keys, **options)
-
- # Store all new attrs so that DNSRecord post callback is called for
- # new attributes only and not for all attributes in the LDAP entry
- setattr(context, 'dnsrecord_precallback_attrs', precallback_attrs)
-
- # We always want to retrieve all DNS record attributes to test for
- # record type collisions (#2601)
- try:
- old_entry = ldap.get_entry(dn, _record_attributes)
- except errors.NotFound:
- old_entry = None
- else:
- for attr in entry_attrs.keys():
- if attr not in _record_attributes:
- continue
- if entry_attrs[attr] is None:
- entry_attrs[attr] = []
- if not isinstance(entry_attrs[attr], (tuple, list)):
- vals = [entry_attrs[attr]]
- else:
- vals = list(entry_attrs[attr])
- entry_attrs[attr] = list(set(old_entry.get(attr, []) + vals))
-
- rrattrs = self.obj.updated_rrattrs(old_entry, entry_attrs)
- self.obj.check_record_type_dependencies(keys, rrattrs)
- self.obj.check_record_type_collisions(keys, rrattrs)
- context.dnsrecord_entry_mods = getattr(context, 'dnsrecord_entry_mods',
- {})
- context.dnsrecord_entry_mods[(keys[0], keys[1])] = entry_attrs.copy()
-
- return dn
-
- def execute(self, *keys, **options):
- result = super(dnsrecord_add, self).execute(*keys, **options)
- self.obj.warning_suspicious_relative_name(result, *keys, **options)
- return result
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- if call_func.__name__ == 'add_entry':
- if isinstance(exc, errors.DuplicateEntry):
- # A new record is being added to existing LDAP DNS object
- # Update can be safely run as old record values has been
- # already merged in pre_callback
- ldap = self.obj.backend
- entry_attrs = self.obj.get_record_entry_attrs(call_args[0])
- update = ldap.get_entry(entry_attrs.dn, list(entry_attrs))
- update.update(entry_attrs)
- ldap.update_entry(update, **call_kwargs)
- return
- raise exc
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- for attr in getattr(context, 'dnsrecord_precallback_attrs', []):
- param = self.params[attr]
- param.dnsrecord_add_post_callback(ldap, dn, entry_attrs, *keys, **options)
-
- if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
-
- self.obj.postprocess_record(entry_attrs, **options)
-
- if self.api.env['wait_for_dns']:
- self.obj.wait_for_modified_entries(context.dnsrecord_entry_mods)
- return dn
-
-
-
-@register()
-class dnsrecord_mod(LDAPUpdate):
- __doc__ = _('Modify a DNS resource record.')
-
- no_option_msg = 'No options to modify a specific record provided.'
-
- takes_options = LDAPUpdate.takes_options + (
- dnsrecord.structured_flag,
- )
-
- def args_options_2_entry(self, *keys, **options):
- has_cli_options(self, options, self.no_option_msg, True)
- return super(dnsrecord_mod, self).args_options_2_entry(*keys, **options)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- if options.get('rename') and self.obj.is_pkey_zone_record(*keys):
- # zone rename is not allowed
- raise errors.ValidationError(name='rename',
- error=_('DNS zone root record cannot be renamed'))
-
- # check if any attr should be updated using structured instead of replaced
- # format is recordname : (old_value, new_parts)
- updated_attrs = {}
- for param in iterate_rrparams_by_parts(self, options, skip_extra=True):
- parts = param.get_parts_from_kw(options, raise_on_none=False)
-
- if parts is None:
- # old-style modification
- continue
-
- old_value = entry_attrs.get(param.name)
- if not old_value:
- raise errors.RequirementError(name=param.name)
- if isinstance(old_value, (tuple, list)):
- if len(old_value) > 1:
- raise errors.ValidationError(name=param.name,
- error=_('DNS records can be only updated one at a time'))
- old_value = old_value[0]
-
- updated_attrs[param.name] = (old_value, parts)
-
- # Run pre_callback validators
- self.obj.run_precallback_validators(dn, entry_attrs, *keys, **options)
-
- # current entry is needed in case of per-dns-record-part updates and
- # for record type collision check
- try:
- old_entry = ldap.get_entry(dn, _record_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if updated_attrs:
- for attr in updated_attrs:
- param = self.params[attr]
- old_dnsvalue, new_parts = updated_attrs[attr]
-
- if old_dnsvalue not in old_entry.get(attr, []):
- attr_name = unicode(param.label or param.name)
- raise errors.AttrValueNotFound(attr=attr_name,
- value=old_dnsvalue)
- old_entry[attr].remove(old_dnsvalue)
-
- old_parts = param._get_part_values(old_dnsvalue)
- modified_parts = tuple(part if part is not None else old_parts[part_id] \
- for part_id,part in enumerate(new_parts))
-
- new_dnsvalue = [param._convert_scalar(modified_parts)]
- entry_attrs[attr] = list(set(old_entry[attr] + new_dnsvalue))
-
- rrattrs = self.obj.updated_rrattrs(old_entry, entry_attrs)
- self.obj.check_record_type_dependencies(keys, rrattrs)
- self.obj.check_record_type_collisions(keys, rrattrs)
-
- context.dnsrecord_entry_mods = getattr(context, 'dnsrecord_entry_mods',
- {})
- context.dnsrecord_entry_mods[(keys[0], keys[1])] = entry_attrs.copy()
- return dn
-
- def execute(self, *keys, **options):
- result = super(dnsrecord_mod, self).execute(*keys, **options)
-
- # remove if empty
- if not self.obj.is_pkey_zone_record(*keys):
- rename = options.get('rename')
- if rename is not None:
- keys = keys[:-1] + (rename,)
- dn = self.obj.get_dn(*keys, **options)
- ldap = self.obj.backend
- old_entry = ldap.get_entry(dn, _record_attributes)
-
- del_all = True
- for attr in old_entry.keys():
- if old_entry[attr]:
- del_all = False
- break
-
- if del_all:
- result = self.obj.methods.delentry(*keys,
- version=options['version'])
-
- # we need to modify delete result to match mod output type
- # only one value is expected, not a list
- if client_has_capability(options['version'], 'primary_key_types'):
- assert len(result['value']) == 1
- result['value'] = result['value'][0]
-
- # indicate that entry was deleted
- context.dnsrecord_entry_mods[(keys[0], keys[1])] = None
-
- if self.api.env['wait_for_dns']:
- self.obj.wait_for_modified_entries(context.dnsrecord_entry_mods)
- if 'nsrecord' in options:
- self.obj.warning_if_ns_change_cause_fwzone_ineffective(result,
- *keys,
- **options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
-
- self.obj.postprocess_record(entry_attrs, **options)
- return dn
-
-
-@register()
-class dnsrecord_delentry(LDAPDelete):
- """
- Delete DNS record entry.
- """
- msg_summary = _('Deleted record "%(value)s"')
- NO_CLI = True
-
-
-
-@register()
-class dnsrecord_del(LDAPUpdate):
- __doc__ = _('Delete DNS resource record.')
-
- has_output = output.standard_multi_delete
-
- 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.")
-
- takes_options = (
- Flag('del_all',
- default=False,
- label=_('Delete all associated records'),
- ),
- dnsrecord.structured_flag,
- )
-
- def get_options(self):
- for option in super(dnsrecord_del, self).get_options():
- if any(flag in option.flags for flag in \
- ('dnsrecord_part', 'dnsrecord_extra',)):
- continue
- elif option.name in ('rename', ):
- # options only valid for dnsrecord-mod
- continue
- elif isinstance(option, DNSRecord):
- yield option.clone(option_group=None)
- continue
- yield option
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- old_entry = ldap.get_entry(dn, _record_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- for attr in entry_attrs.keys():
- if attr not in _record_attributes:
- continue
- if not isinstance(entry_attrs[attr], (tuple, list)):
- vals = [entry_attrs[attr]]
- else:
- vals = entry_attrs[attr]
-
- for val in vals:
- try:
- old_entry[attr].remove(val)
- except (KeyError, ValueError):
- try:
- param = self.params[attr]
- attr_name = unicode(param.label or param.name)
- except Exception:
- attr_name = attr
- raise errors.AttrValueNotFound(attr=attr_name, value=val)
- entry_attrs[attr] = list(set(old_entry[attr]))
-
- rrattrs = self.obj.updated_rrattrs(old_entry, entry_attrs)
- self.obj.check_record_type_dependencies(keys, rrattrs)
-
- del_all = False
- if not self.obj.is_pkey_zone_record(*keys):
- record_found = False
- for attr in old_entry.keys():
- if old_entry[attr]:
- record_found = True
- break
- del_all = not record_found
-
- # set del_all flag in context
- # when the flag is enabled, the entire DNS record object is deleted
- # in a post callback
- context.del_all = del_all
- context.dnsrecord_entry_mods = getattr(context, 'dnsrecord_entry_mods',
- {})
- context.dnsrecord_entry_mods[(keys[0], keys[1])] = entry_attrs.copy()
-
- return dn
-
- def execute(self, *keys, **options):
- if options.get('del_all', False):
- if self.obj.is_pkey_zone_record(*keys):
- raise errors.ValidationError(
- name='del_all',
- error=_('Zone record \'%s\' cannot be deleted') \
- % _dns_zone_record
- )
- result = self.obj.methods.delentry(*keys,
- version=options['version'])
- if self.api.env['wait_for_dns']:
- entries = {(keys[0], keys[1]): None}
- self.obj.wait_for_modified_entries(entries)
- else:
- result = super(dnsrecord_del, self).execute(*keys, **options)
- result['value'] = pkey_to_value([keys[-1]], options)
-
- if getattr(context, 'del_all', False) and not \
- self.obj.is_pkey_zone_record(*keys):
- result = self.obj.methods.delentry(*keys,
- version=options['version'])
- context.dnsrecord_entry_mods[(keys[0], keys[1])] = None
-
- if self.api.env['wait_for_dns']:
- self.obj.wait_for_modified_entries(context.dnsrecord_entry_mods)
-
- if 'nsrecord' in options or options.get('del_all', False):
- self.obj.warning_if_ns_change_cause_fwzone_ineffective(result,
- *keys,
- **options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
- self.obj.postprocess_record(entry_attrs, **options)
- return dn
-
- def args_options_2_entry(self, *keys, **options):
- has_cli_options(self, options, self.no_option_msg)
- return super(dnsrecord_del, self).args_options_2_entry(*keys, **options)
-
-
-@register()
-class dnsrecord_show(LDAPRetrieve):
- __doc__ = _('Display DNS resource.')
-
- takes_options = LDAPRetrieve.takes_options + (
- dnsrecord.structured_flag,
- )
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if self.obj.is_pkey_zone_record(*keys):
- entry_attrs[self.obj.primary_key.name] = [_dns_zone_record]
- self.obj.postprocess_record(entry_attrs, **options)
- return dn
-
-
-
-@register()
-class dnsrecord_find(LDAPSearch):
- __doc__ = _('Search for DNS resources.')
-
- takes_options = LDAPSearch.takes_options + (
- dnsrecord.structured_flag,
- )
-
- def get_options(self):
- for option in super(dnsrecord_find, self).get_options():
- if any(flag in option.flags for flag in \
- ('dnsrecord_part', 'dnsrecord_extra',)):
- continue
- elif isinstance(option, DNSRecord):
- yield option.clone(option_group=None)
- continue
- yield option
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope,
- dnszoneidnsname, *args, **options):
- assert isinstance(base_dn, DN)
-
- # validate if zone is master zone
- self.obj.check_zone(dnszoneidnsname, **options)
-
- filter = _create_idn_filter(self, ldap, *args, **options)
- return (filter, base_dn, ldap.SCOPE_SUBTREE)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if entries:
- zone_obj = self.api.Object[self.obj.parent_object]
- zone_dn = zone_obj.get_dn(args[0])
- if entries[0].dn == zone_dn:
- entries[0][zone_obj.primary_key.name] = [_dns_zone_record]
- for entry in entries:
- self.obj.postprocess_record(entry, **options)
-
- return truncated
-
-
-@register()
-class dns_resolve(Command):
- __doc__ = _('Resolve a host name in DNS. (Deprecated)')
-
- NO_CLI = True
-
- has_output = output.standard_value
- msg_summary = _('Found \'%(value)s\'')
-
- takes_args = (
- Str('hostname',
- label=_('Hostname (FQDN)'),
- ),
- )
-
- def execute(self, *args, **options):
- query=args[0]
-
- try:
- verify_host_resolvable(query)
- except errors.DNSNotARecordError:
- raise errors.NotFound(
- reason=_('Host \'%(host)s\' not found') % {'host': query}
- )
- result = dict(result=True, value=query)
- messages.add_message(
- options['version'], result,
- messages.CommandDeprecatedWarning(
- command='dns-resolve',
- additional_info='The command may return an unexpected result, '
- 'the resolution of the DNS domain is done on '
- 'a randomly chosen IPA server.'
- )
- )
- return result
-
-
-@register()
-class dns_is_enabled(Command):
- """
- Checks if any of the servers has the DNS service enabled.
- """
- NO_CLI = True
- has_output = output.standard_value
-
- base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
- filter = '(&(objectClass=ipaConfigObject)(cn=DNS))'
-
- def execute(self, *args, **options):
- ldap = self.api.Backend.ldap2
- dns_enabled = False
-
- try:
- ldap.find_entries(filter=self.filter, base_dn=self.base_dn)
- dns_enabled = True
- except errors.EmptyResult:
- dns_enabled = False
-
- return dict(result=dns_enabled, value=pkey_to_value(None, options))
-
-
-@register()
-class dnsconfig(LDAPObject):
- """
- DNS global configuration object
- """
- object_name = _('DNS configuration options')
- default_attributes = [
- 'idnsforwardpolicy', 'idnsforwarders', 'idnsallowsyncptr'
- ]
-
- label = _('DNS Global Configuration')
- label_singular = _('DNS Global Configuration')
-
- takes_params = (
- Str('idnsforwarders*',
- _validate_bind_forwarder,
- cli_name='forwarder',
- label=_('Global forwarders'),
- doc=_('Global forwarders. A custom port can be specified for each '
- 'forwarder using a standard format "IP_ADDRESS port PORT"'),
- ),
- StrEnum('idnsforwardpolicy?',
- cli_name='forward_policy',
- label=_('Forward policy'),
- doc=_('Global forwarding policy. Set to "none" to disable '
- 'any configured global forwarders.'),
- values=(u'only', u'first', u'none'),
- ),
- Bool('idnsallowsyncptr?',
- cli_name='allow_sync_ptr',
- label=_('Allow PTR sync'),
- doc=_('Allow synchronization of forward (A, AAAA) and reverse (PTR) records'),
- ),
- Int('idnszonerefresh?',
- deprecated=True,
- cli_name='zone_refresh',
- label=_('Zone refresh interval'),
- doc=_('An interval between regular polls of the name server for new DNS zones'),
- minvalue=0,
- flags={'no_option'},
- ),
- Int('ipadnsversion?', # available only in installer/upgrade
- label=_('IPA DNS version'),
- ),
- )
- managed_permissions = {
- 'System: Write DNS Configuration': {
- 'non_object': True,
- 'ipapermright': {'write'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=dns', api.env.basedn),
- 'ipapermtargetfilter': ['(objectclass=idnsConfigObject)'],
- 'ipapermdefaultattr': {
- 'idnsallowsyncptr', 'idnsforwarders', 'idnsforwardpolicy',
- 'idnspersistentsearch', 'idnszonerefresh'
- },
- 'replaces': [
- '(targetattr = "idnsforwardpolicy || idnsforwarders || idnsallowsyncptr || idnszonerefresh || idnspersistentsearch")(target = "ldap:///cn=dns,$SUFFIX")(version 3.0;acl "permission:Write DNS Configuration";allow (write) groupdn = "ldap:///cn=Write DNS Configuration,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- 'System: Read DNS Configuration': {
- 'non_object': True,
- 'ipapermright': {'read'},
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=dns', api.env.basedn),
- 'ipapermtargetfilter': ['(objectclass=idnsConfigObject)'],
- 'ipapermdefaultattr': {
- 'objectclass',
- 'idnsallowsyncptr', 'idnsforwarders', 'idnsforwardpolicy',
- 'idnspersistentsearch', 'idnszonerefresh', 'ipadnsversion'
- },
- 'default_privileges': {'DNS Administrators', 'DNS Servers'},
- },
- }
-
- def get_dn(self, *keys, **kwargs):
- if not dns_container_exists(self.api.Backend.ldap2):
- raise errors.NotFound(reason=_('DNS is not configured'))
- return DN(api.env.container_dns, api.env.basedn)
-
- def get_dnsconfig(self, ldap):
- entry = ldap.get_entry(self.get_dn(), None)
-
- return entry
-
- def postprocess_result(self, result):
- if not any(param in result['result'] for param in self.params):
- result['summary'] = unicode(_('Global DNS configuration is empty'))
-
-
-@register()
-class dnsconfig_mod(LDAPUpdate):
- __doc__ = _('Modify global DNS configuration.')
-
- def get_options(self):
- """hide ipadnsversion outside of installer/upgrade"""
- for option in super(dnsconfig_mod, self).get_options():
- if option.name == 'ipadnsversion':
- option = option.clone(include=('installer', 'updates'))
- yield option
-
- def execute(self, *keys, **options):
- # test dnssec forwarders
- forwarders = options.get('idnsforwarders')
-
- result = super(dnsconfig_mod, self).execute(*keys, **options)
- self.obj.postprocess_result(result)
-
- # this check makes sense only when resulting forwarders are non-empty
- if result['result'].get('idnsforwarders'):
- fwzone = DNSName('.')
- _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
-
- if forwarders:
- # forwarders were changed
- for forwarder in forwarders:
- try:
- validate_dnssec_global_forwarder(forwarder, log=self.log)
- except DNSSECSignatureMissingError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerDoesNotSupportDNSSECWarning(
- server=forwarder, error=e,
- )
- )
- except EDNS0UnsupportedError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerDoesNotSupportEDNS0Warning(
- server=forwarder, error=e,
- )
- )
- except UnresolvableRecordError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerValidationWarning(
- server=forwarder, error=e
- )
- )
-
- return result
-
-
-
-@register()
-class dnsconfig_show(LDAPRetrieve):
- __doc__ = _('Show the current global DNS configuration.')
-
- def execute(self, *keys, **options):
- result = super(dnsconfig_show, self).execute(*keys, **options)
- self.obj.postprocess_result(result)
- return result
-
-
-
-@register()
-class dnsforwardzone(DNSZoneBase):
- """
- DNS Forward zone, container for resource records.
- """
- object_name = _('DNS forward zone')
- object_name_plural = _('DNS forward zones')
- object_class = DNSZoneBase.object_class + ['idnsforwardzone']
- label = _('DNS Forward Zones')
- label_singular = _('DNS Forward Zone')
- default_forward_policy = u'first'
-
- # managed_permissions: permissions was apllied in dnszone class, do NOT
- # add them here, they should not be applied twice.
-
- def _warning_fw_zone_is_not_effective(self, result, *keys, **options):
- fwzone = keys[-1]
- _add_warning_fw_zone_is_not_effective(self.api, result, fwzone,
- options['version'])
-
- def _warning_if_forwarders_do_not_work(self, result, new_zone,
- *keys, **options):
- fwzone = keys[-1]
- forwarders = options.get('idnsforwarders', [])
- any_forwarder_work = False
-
- for forwarder in forwarders:
- try:
- validate_dnssec_zone_forwarder_step1(forwarder, fwzone,
- log=self.log)
- except UnresolvableRecordError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerValidationWarning(
- server=forwarder, error=e
- )
- )
- except EDNS0UnsupportedError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerDoesNotSupportEDNS0Warning(
- server=forwarder, error=e
- )
- )
- else:
- any_forwarder_work = True
-
- if not any_forwarder_work:
- # do not test DNSSEC validation if there is no valid forwarder
- return
-
- # resolve IP address of any DNS replica
- # FIXME: https://fedorahosted.org/bind-dyndb-ldap/ticket/143
- # we currenly should to test all IPA DNS replica, because DNSSEC
- # validation is configured just in named.conf per replica
-
- ipa_dns_masters = [normalize_zone(x) for x in
- self.api.Object.dnsrecord.get_dns_masters()]
-
- if not ipa_dns_masters:
- # something very bad happened, DNS is installed, but no IPA DNS
- # servers available
- self.log.error("No IPA DNS server can be found, but integrated DNS "
- "is installed")
- return
-
- ipa_dns_ip = None
- for rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA):
- try:
- ans = dns.resolver.query(ipa_dns_masters[0], rdtype)
- except dns.exception.DNSException:
- continue
- else:
- ipa_dns_ip = str(ans.rrset.items[0])
- break
-
- if not ipa_dns_ip:
- self.log.error("Cannot resolve %s hostname", ipa_dns_masters[0])
- return
-
- # sleep a bit, adding new zone to BIND from LDAP may take a while
- if new_zone:
- time.sleep(5)
-
- # Test if IPA is able to receive replies from forwarders
- try:
- validate_dnssec_zone_forwarder_step2(ipa_dns_ip, fwzone,
- log=self.log)
- except DNSSECValidationError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSSECValidationFailingWarning(error=e)
- )
- except UnresolvableRecordError as e:
- messages.add_message(
- options['version'],
- result, messages.DNSServerValidationWarning(
- server=ipa_dns_ip, error=e
- )
- )
-
-
-@register()
-class dnsforwardzone_add(DNSZoneBase_add):
- __doc__ = _('Create new DNS forward zone.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- dn = super(dnsforwardzone_add, self).pre_callback(ldap, dn,
- entry_attrs, attrs_list, *keys, **options)
-
- if 'idnsforwardpolicy' not in entry_attrs:
- entry_attrs['idnsforwardpolicy'] = self.obj.default_forward_policy
-
- if (not entry_attrs.get('idnsforwarders') and
- entry_attrs['idnsforwardpolicy'] != u'none'):
- raise errors.ValidationError(name=u'idnsforwarders',
- error=_('Please specify forwarders.'))
-
- return dn
-
- def execute(self, *keys, **options):
- fwzone = keys[-1]
- result = super(dnsforwardzone_add, self).execute(*keys, **options)
- self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
- _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
- if options.get('idnsforwarders'):
- self.obj._warning_if_forwarders_do_not_work(
- result, True, *keys, **options)
- return result
-
-
-@register()
-class dnsforwardzone_del(DNSZoneBase_del):
- __doc__ = _('Delete DNS forward zone.')
-
- msg_summary = _('Deleted DNS forward zone "%(value)s"')
-
-
-@register()
-class dnsforwardzone_mod(DNSZoneBase_mod):
- __doc__ = _('Modify DNS forward zone.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- try:
- entry = ldap.get_entry(dn)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if not _check_entry_objectclass(entry, self.obj.object_class):
- self.obj.handle_not_found(*keys)
-
- policy = self.obj.default_forward_policy
- forwarders = []
-
- if 'idnsforwarders' in entry_attrs:
- forwarders = entry_attrs['idnsforwarders']
- elif 'idnsforwarders' in entry:
- forwarders = entry['idnsforwarders']
-
- if 'idnsforwardpolicy' in entry_attrs:
- policy = entry_attrs['idnsforwardpolicy']
- elif 'idnsforwardpolicy' in entry:
- policy = entry['idnsforwardpolicy']
-
- if not forwarders and policy != u'none':
- raise errors.ValidationError(name=u'idnsforwarders',
- error=_('Please specify forwarders.'))
-
- return dn
-
- def execute(self, *keys, **options):
- fwzone = keys[-1]
- result = super(dnsforwardzone_mod, self).execute(*keys, **options)
- _add_warning_fw_policy_conflict_aez(result, fwzone, **options)
- if options.get('idnsforwarders'):
- self.obj._warning_if_forwarders_do_not_work(result, False, *keys,
- **options)
- return result
-
-@register()
-class dnsforwardzone_find(DNSZoneBase_find):
- __doc__ = _('Search for DNS forward zones.')
-
-
-@register()
-class dnsforwardzone_show(DNSZoneBase_show):
- __doc__ = _('Display information about a DNS forward zone.')
-
- has_output_params = LDAPRetrieve.has_output_params + dnszone_output_params
-
-
-@register()
-class dnsforwardzone_disable(DNSZoneBase_disable):
- __doc__ = _('Disable DNS Forward Zone.')
- msg_summary = _('Disabled DNS forward zone "%(value)s"')
-
-
-@register()
-class dnsforwardzone_enable(DNSZoneBase_enable):
- __doc__ = _('Enable DNS Forward Zone.')
- msg_summary = _('Enabled DNS forward zone "%(value)s"')
-
- def execute(self, *keys, **options):
- result = super(dnsforwardzone_enable, self).execute(*keys, **options)
- self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
- return result
-
-
-@register()
-class dnsforwardzone_add_permission(DNSZoneBase_add_permission):
- __doc__ = _('Add a permission for per-forward zone access delegation.')
-
-
-@register()
-class dnsforwardzone_remove_permission(DNSZoneBase_remove_permission):
- __doc__ = _('Remove a permission for per-forward zone access delegation.')
diff --git a/ipalib/plugins/domainlevel.py b/ipalib/plugins/domainlevel.py
deleted file mode 100644
index 23fa2a1b2..000000000
--- a/ipalib/plugins/domainlevel.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-from collections import namedtuple
-
-from ipalib import _
-from ipalib import Command
-from ipalib import errors
-from ipalib import output
-from ipalib.parameters import Int
-from ipalib.plugable import Registry
-
-from ipapython.dn import DN
-
-
-__doc__ = _("""
-Raise the IPA Domain Level.
-""")
-
-register = Registry()
-
-DomainLevelRange = namedtuple('DomainLevelRange', ['min', 'max'])
-
-domainlevel_output = (
- output.Output('result', int, _('Current domain level:')),
-)
-
-
-def get_domainlevel_dn(api):
- domainlevel_dn = DN(
- ('cn', 'Domain Level'),
- ('cn', 'ipa'),
- ('cn', 'etc'),
- api.env.basedn
- )
-
- return domainlevel_dn
-
-
-def get_domainlevel_range(master_entry):
- try:
- return DomainLevelRange(
- int(master_entry['ipaMinDomainLevel'][0]),
- int(master_entry['ipaMaxDomainLevel'][0])
- )
- except KeyError:
- return DomainLevelRange(0, 0)
-
-
-def get_master_entries(ldap, api):
- """
- Returns list of LDAPEntries representing IPA masters.
- """
-
- container_masters = DN(
- ('cn', 'masters'),
- ('cn', 'ipa'),
- ('cn', 'etc'),
- api.env.basedn
- )
-
- masters, _ = ldap.find_entries(
- filter="(cn=*)",
- base_dn=container_masters,
- scope=ldap.SCOPE_ONELEVEL,
- paged_search=True, # we need to make sure to get all of them
- )
-
- return masters
-
-
-@register()
-class domainlevel_get(Command):
- __doc__ = _('Query current Domain Level.')
-
- has_output = domainlevel_output
-
- def execute(self, *args, **options):
- ldap = self.api.Backend.ldap2
- entry = ldap.get_entry(
- get_domainlevel_dn(self.api),
- ['ipaDomainLevel']
- )
-
- return {'result': int(entry.single_value['ipaDomainLevel'])}
-
-
-@register()
-class domainlevel_set(Command):
- __doc__ = _('Change current Domain Level.')
-
- has_output = domainlevel_output
-
- takes_args = (
- Int('ipadomainlevel',
- cli_name='level',
- label=_('Domain Level'),
- minvalue=0,
- ),
- )
-
- def execute(self, *args, **options):
- """
- Checks all the IPA masters for supported domain level ranges.
-
- If the desired domain level is within the supported range of all
- masters, it will be raised.
-
- Domain level cannot be lowered.
- """
-
- ldap = self.api.Backend.ldap2
-
- current_entry = ldap.get_entry(get_domainlevel_dn(self.api))
- current_value = int(current_entry.single_value['ipadomainlevel'])
- desired_value = int(args[0])
-
- # Domain level cannot be lowered
- if int(desired_value) < int(current_value):
- message = _("Domain Level cannot be lowered.")
- raise errors.InvalidDomainLevelError(reason=message)
-
- # Check if every master supports the desired level
- for master in get_master_entries(ldap, self.api):
- supported = get_domainlevel_range(master)
-
- if supported.min > desired_value or supported.max < desired_value:
- message = _("Domain Level cannot be raised to {0}, server {1} "
- "does not support it."
- .format(desired_value, master['cn'][0]))
- raise errors.InvalidDomainLevelError(reason=message)
-
- current_entry.single_value['ipaDomainLevel'] = desired_value
- ldap.update_entry(current_entry)
-
- return {'result': int(current_entry.single_value['ipaDomainLevel'])}
diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py
deleted file mode 100644
index 2b0c08050..000000000
--- a/ipalib/plugins/group.py
+++ /dev/null
@@ -1,690 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# 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 ipalib import api
-from ipalib import Int, Str, Flag
-from ipalib.plugable import Registry
-from .baseldap import (
- add_external_post_callback,
- pkey_to_value,
- remove_external_post_callback,
- LDAPObject,
- LDAPCreate,
- LDAPUpdate,
- LDAPDelete,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember,
- LDAPQuery,
-)
-from .idviews import remove_ipaobject_overrides
-from . import baseldap
-from ipalib import _, ngettext
-from ipalib import errors
-from ipalib import output
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- import ipaserver.dcerpc
- _dcerpc_bindings_installed = True
- except ImportError:
- _dcerpc_bindings_installed = False
-
-__doc__ = _("""
-Groups of users
-
-Manage groups of users. By default, new groups are POSIX groups. You
-can add the --nonposix option to the group-add command to mark a new group
-as non-POSIX. You can use the --posix argument with the group-mod command
-to convert a non-POSIX group into a POSIX group. POSIX groups cannot be
-converted to non-POSIX groups.
-
-Every group must have a description.
-
-POSIX groups must have a Group ID (GID) number. Changing a GID is
-supported but can have an impact on your file permissions. It is not necessary
-to supply a GID when creating a group. IPA will generate one automatically
-if it is not provided.
-
-EXAMPLES:
-
- Add a new group:
- ipa group-add --desc='local administrators' localadmins
-
- Add a new non-POSIX group:
- ipa group-add --nonposix --desc='remote administrators' remoteadmins
-
- Convert a non-POSIX group to posix:
- ipa group-mod --posix remoteadmins
-
- Add a new POSIX group with a specific Group ID number:
- ipa group-add --gid=500 --desc='unix admins' unixadmins
-
- Add a new POSIX group and let IPA assign a Group ID number:
- ipa group-add --desc='printer admins' printeradmins
-
- Remove a group:
- ipa group-del unixadmins
-
- To add the "remoteadmins" group to the "localadmins" group:
- ipa group-add-member --groups=remoteadmins localadmins
-
- Add multiple users to the "localadmins" group:
- ipa group-add-member --users=test1 --users=test2 localadmins
-
- Remove a user from the "localadmins" group:
- ipa group-remove-member --users=test2 localadmins
-
- Display information about a named group.
- ipa group-show localadmins
-
-External group membership is designed to allow users from trusted domains
-to be mapped to local POSIX groups in order to actually use IPA resources.
-External members should be added to groups that specifically created as
-external and non-POSIX. Such group later should be included into one of POSIX
-groups.
-
-An external group member is currently a Security Identifier (SID) as defined by
-the trusted domain. When adding external group members, it is possible to
-specify them in either SID, or DOM\\name, or name@domain format. IPA will attempt
-to resolve passed name to SID with the use of Global Catalog of the trusted domain.
-
-Example:
-
-1. Create group for the trusted domain admins' mapping and their local POSIX group:
-
- ipa group-add --desc='<ad.domain> admins external map' ad_admins_external --external
- ipa group-add --desc='<ad.domain> admins' ad_admins
-
-2. Add security identifier of Domain Admins of the <ad.domain> to the ad_admins_external
- group:
-
- ipa group-add-member ad_admins_external --external 'AD\\Domain Admins'
-
-3. Allow members of ad_admins_external group to be associated with ad_admins POSIX group:
-
- ipa group-add-member ad_admins --groups ad_admins_external
-
-4. List members of external members of ad_admins_external group to see their SIDs:
-
- ipa group-show ad_admins_external
-""")
-
-register = Registry()
-
-PROTECTED_GROUPS = (u'admins', u'trust admins', u'default smb group')
-
-
-@register()
-class group(LDAPObject):
- """
- Group object.
- """
- container_dn = api.env.container_group
- object_name = _('group')
- object_name_plural = _('groups')
- object_class = ['ipausergroup']
- object_class_config = 'ipagroupobjectclasses'
- possible_objectclasses = ['posixGroup', 'mepManagedEntry', 'ipaExternalGroup']
- permission_filter_objectclasses = ['posixgroup', 'ipausergroup']
- search_attributes_config = 'ipagroupsearchfields'
- default_attributes = [
- 'cn', 'description', 'gidnumber', 'member', 'memberof',
- 'memberindirect', 'memberofindirect', 'ipaexternalmember',
- ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'member': ['user', 'group'],
- 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
- 'memberindirect': ['user', 'group'],
- 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule',
- 'sudorule'],
- }
- rdn_is_primary_key = True
- managed_permissions = {
- 'System: Read Groups': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'gidnumber',
- 'ipaexternalmember', 'ipauniqueid', 'mepmanagedby', 'o',
- 'objectclass', 'ou', 'owner', 'seealso',
- 'ipantsecurityidentifier'
- },
- },
- 'System: Read Group Membership': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'member', 'memberof', 'memberuid', 'memberuser', 'memberhost',
- },
- },
- 'System: Add Groups': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Groups";allow (add) groupdn = "ldap:///cn=Add Groups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Group Administrators'},
- },
- 'System: Modify Group Membership': {
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': [
- '(objectclass=ipausergroup)',
- '(!(cn=admins))',
- ],
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(!(cn=admins))")(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {
- 'Group Administrators', 'Modify Group membership'
- },
- },
- 'System: Modify Groups': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'gidnumber', 'ipauniqueid',
- 'mepmanagedby', 'objectclass'
- },
- 'replaces': [
- '(targetattr = "cn || description || gidnumber || objectclass || mepmanagedby || ipauniqueid")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Groups";allow (write) groupdn = "ldap:///cn=Modify Groups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Group Administrators'},
- },
- 'System: Remove Groups': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Groups";allow (delete) groupdn = "ldap:///cn=Remove Groups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Group Administrators'},
- },
- 'System: Read Group Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=groups', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'memberuid', 'gidnumber',
- },
- },
- 'System: Read Group Views Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=groups', 'cn=*', 'cn=views', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'memberuid', 'gidnumber',
- },
- },
- }
-
- label = _('User Groups')
- label_singular = _('User Group')
-
- takes_params = (
- Str('cn',
- pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
- pattern_errmsg='may only include letters, numbers, _, -, . and $',
- maxlength=255,
- cli_name='group_name',
- label=_('Group name'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Group description'),
- ),
- Int('gidnumber?',
- cli_name='gid',
- label=_('GID'),
- doc=_('GID (use this option to set it manually)'),
- minvalue=1,
- ),
- )
-
-
-ipaexternalmember_param = Str('ipaexternalmember*',
- cli_name='external',
- label=_('External member'),
- doc=_('Members of a trusted domain in DOM\\name or name@domain form'),
- flags=['no_create', 'no_update', 'no_search'],
- )
-
-
-@register()
-class group_add(LDAPCreate):
- __doc__ = _('Create a new group.')
-
- msg_summary = _('Added group "%(value)s"')
-
- takes_options = LDAPCreate.takes_options + (
- Flag('nonposix',
- cli_name='nonposix',
- doc=_('Create as a non-POSIX group'),
- default=False,
- ),
- Flag('external',
- cli_name='external',
- doc=_('Allow adding external non-IPA members from trusted domains'),
- default=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- # As both 'external' and 'nonposix' options have default= set for
- # them, they will always be present in options dict, thus we can
- # safely reference the values
- assert isinstance(dn, DN)
- if options['external']:
- entry_attrs['objectclass'].append('ipaexternalgroup')
- if 'gidnumber' in options:
- raise errors.MutuallyExclusiveError(reason=_('gid cannot be set for external group'))
- elif not options['nonposix']:
- entry_attrs['objectclass'].append('posixgroup')
- if not 'gidnumber' in options:
- entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
- return dn
-
-
-@register()
-class group_del(LDAPDelete):
- __doc__ = _('Delete group.')
-
- msg_summary = _('Deleted group "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- config = ldap.get_ipa_config()
- def_primary_group = config.get('ipadefaultprimarygroup', '')
- def_primary_group_dn = group_dn = self.obj.get_dn(def_primary_group)
- if dn == def_primary_group_dn:
- raise errors.DefaultGroupError()
- group_attrs = self.obj.methods.show(
- self.obj.get_primary_key_from_dn(dn), all=True
- )['result']
- if keys[0] in PROTECTED_GROUPS:
- raise errors.ProtectedEntryError(label=_(u'group'), key=keys[0],
- reason=_(u'privileged group'))
- if 'mepmanagedby' in group_attrs:
- raise errors.ManagedGroupError()
-
- # Remove any ID overrides tied with this group
- remove_ipaobject_overrides(ldap, self.obj.api, dn)
-
- return dn
-
- def post_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- try:
- api.Command['pwpolicy_del'](keys[-1])
- except errors.NotFound:
- pass
-
- return True
-
-
-@register()
-class group_mod(LDAPUpdate):
- __doc__ = _('Modify a group.')
-
- msg_summary = _('Modified group "%(value)s"')
-
- takes_options = LDAPUpdate.takes_options + (
- Flag('posix',
- cli_name='posix',
- doc=_('change to a POSIX group'),
- ),
- Flag('external',
- cli_name='external',
- doc=_('change to support external non-IPA members from trusted domains'),
- default=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- is_protected_group = keys[-1] in PROTECTED_GROUPS
-
- if 'rename' in options or 'cn' in entry_attrs:
- if is_protected_group:
- raise errors.ProtectedEntryError(label=u'group', key=keys[-1],
- reason=u'Cannot be renamed')
-
- if ('posix' in options and options['posix']) or 'gidnumber' in options:
- old_entry_attrs = ldap.get_entry(dn, ['objectclass'])
- dn = old_entry_attrs.dn
- if 'ipaexternalgroup' in old_entry_attrs['objectclass']:
- raise errors.ExternalGroupViolation()
- if 'posixgroup' in old_entry_attrs['objectclass']:
- if options['posix']:
- raise errors.AlreadyPosixGroup()
- else:
- old_entry_attrs['objectclass'].append('posixgroup')
- entry_attrs['objectclass'] = old_entry_attrs['objectclass']
- if not 'gidnumber' in options:
- entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
-
- if options['external']:
- if is_protected_group:
- raise errors.ProtectedEntryError(label=u'group', key=keys[-1],
- reason=u'Cannot support external non-IPA members')
- old_entry_attrs = ldap.get_entry(dn, ['objectclass'])
- dn = old_entry_attrs.dn
- if 'posixgroup' in old_entry_attrs['objectclass']:
- raise errors.PosixGroupViolation()
- if 'ipaexternalgroup' in old_entry_attrs['objectclass']:
- raise errors.AlreadyExternalGroup()
- else:
- old_entry_attrs['objectclass'].append('ipaexternalgroup')
- entry_attrs['objectclass'] = old_entry_attrs['objectclass']
-
- # Can't check for this in a validator because we lack context
- if 'gidnumber' in options and options['gidnumber'] is None:
- raise errors.RequirementError(name='gidnumber')
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- # Check again for GID requirement in case someone tried to clear it
- # using --setattr.
- if call_func.__name__ == 'update_entry':
- if isinstance(exc, errors.ObjectclassViolation):
- if 'gidNumber' in exc.message and 'posixGroup' in exc.message:
- raise errors.RequirementError(name='gidnumber')
- raise exc
-
-
-@register()
-class group_find(LDAPSearch):
- __doc__ = _('Search for groups.')
-
- member_attributes = ['member', 'memberof']
-
- msg_summary = ngettext(
- '%(count)d group matched', '%(count)d groups matched', 0
- )
-
- takes_options = LDAPSearch.takes_options + (
- Flag('private',
- cli_name='private',
- doc=_('search for private groups'),
- ),
- Flag('posix',
- cli_name='posix',
- doc=_('search for POSIX groups'),
- ),
- Flag('external',
- cli_name='external',
- doc=_('search for groups with support of external non-IPA members from trusted domains'),
- ),
- Flag('nonposix',
- cli_name='nonposix',
- doc=_('search for non-POSIX groups'),
- ),
- )
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope,
- criteria=None, **options):
- assert isinstance(base_dn, DN)
-
- # filter groups by pseudo type
- filters = []
- if options['posix']:
- search_kw = {'objectclass': ['posixGroup']}
- filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_ALL))
- if options['external']:
- search_kw = {'objectclass': ['ipaExternalGroup']}
- filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_ALL))
- if options['nonposix']:
- search_kw = {'objectclass': ['posixGroup' , 'ipaExternalGroup']}
- filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_NONE))
-
- # if looking for private groups, we need to create a new search filter,
- # because private groups have different object classes
- if options['private']:
- # filter based on options, oflt
- search_kw = self.args_options_2_entry(**options)
- search_kw['objectclass'] = ['posixGroup', 'mepManagedEntry']
- oflt = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
-
- # filter based on 'criteria' argument
- search_kw = {}
- config = ldap.get_ipa_config()
- attrs = config.get(self.obj.search_attributes_config, [])
- if len(attrs) == 1 and isinstance(attrs[0], six.string_types):
- search_attrs = attrs[0].split(',')
- for a in search_attrs:
- search_kw[a] = criteria
- cflt = ldap.make_filter(search_kw, exact=False)
-
- filter = ldap.combine_filters((oflt, cflt), rules=ldap.MATCH_ALL)
- elif filters:
- filters.append(filter)
- filter = ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
- return (filter, base_dn, scope)
-
-
-@register()
-class group_show(LDAPRetrieve):
- __doc__ = _('Display information about a named group.')
- has_output_params = LDAPRetrieve.has_output_params + (ipaexternalmember_param,)
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if ('ipaexternalmember' in entry_attrs and
- len(entry_attrs['ipaexternalmember']) > 0 and
- 'trust_resolve' in self.Command and
- not options.get('raw', False)):
- sids = entry_attrs['ipaexternalmember']
- result = self.Command.trust_resolve(sids=sids)
- for entry in result['result']:
- try:
- idx = sids.index(entry['sid'][0])
- sids[idx] = entry['name'][0]
- except ValueError:
- pass
- return dn
-
-
-@register()
-class group_add_member(LDAPAddMember):
- __doc__ = _('Add members to a group.')
-
- takes_options = (ipaexternalmember_param,)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- result = (completed, dn)
- if 'ipaexternalmember' in options:
- if not _dcerpc_bindings_installed:
- raise errors.NotFound(reason=_('Cannot perform external member validation without '
- 'Samba 4 support installed. Make sure you have installed '
- 'server-trust-ad sub-package of IPA on the server'))
- domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
- if not domain_validator.is_configured():
- raise errors.NotFound(reason=_('Cannot perform join operation without own domain configured. '
- 'Make sure you have run ipa-adtrust-install on the IPA server first'))
- sids = []
- failed_sids = []
- for sid in options['ipaexternalmember']:
- if domain_validator.is_trusted_sid_valid(sid):
- sids.append(sid)
- else:
- try:
- actual_sid = domain_validator.get_trusted_domain_object_sid(sid)
- except errors.PublicError as e:
- failed_sids.append((sid, e.strerror))
- else:
- sids.append(actual_sid)
- restore = []
- if 'member' in failed and 'group' in failed['member']:
- restore = failed['member']['group']
- failed['member']['group'] = list((id, id) for id in sids)
- result = add_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='member',
- membertype='group',
- externalattr='ipaexternalmember',
- normalize=False)
- failed['member']['group'] += restore + failed_sids
- return result
-
-
-@register()
-class group_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from a group.')
-
- takes_options = (ipaexternalmember_param,)
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- if keys[0] in PROTECTED_GROUPS and 'user' in options:
- protected_group_name = keys[0]
- result = api.Command.group_show(protected_group_name)
- users_left = set(result['result'].get('member_user', []))
- users_deleted = set(options['user'])
- if users_left.issubset(users_deleted):
- raise errors.LastMemberError(key=sorted(users_deleted)[0],
- label=_(u'group'), container=protected_group_name)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- result = (completed, dn)
- if 'ipaexternalmember' in options:
- if not _dcerpc_bindings_installed:
- raise errors.NotFound(reason=_('Cannot perform external member validation without '
- 'Samba 4 support installed. Make sure you have installed '
- 'server-trust-ad sub-package of IPA on the server'))
- domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
- if not domain_validator.is_configured():
- raise errors.NotFound(reason=_('Cannot perform join operation without own domain configured. '
- 'Make sure you have run ipa-adtrust-install on the IPA server first'))
- sids = []
- failed_sids = []
- for sid in options['ipaexternalmember']:
- if domain_validator.is_trusted_sid_valid(sid):
- sids.append(sid)
- else:
- try:
- actual_sid = domain_validator.get_trusted_domain_object_sid(sid)
- except errors.PublicError as e:
- failed_sids.append((sid, unicode(e)))
- else:
- sids.append(actual_sid)
- restore = []
- if 'member' in failed and 'group' in failed['member']:
- restore = failed['member']['group']
- failed['member']['group'] = list((id, id) for id in sids)
- result = remove_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='member',
- membertype='group',
- externalattr='ipaexternalmember',
- )
- failed['member']['group'] += restore + failed_sids
- return result
-
-
-@register()
-class group_detach(LDAPQuery):
- __doc__ = _('Detach a managed group from a user.')
-
- has_output = output.standard_value
- msg_summary = _('Detached group "%(value)s" from user "%(value)s"')
-
- def execute(self, *keys, **options):
- """
- This requires updating both the user and the group. We first need to
- verify that both the user and group can be updated, then we go
- about our work. We don't want a situation where only the user or
- group can be modified and we're left in a bad state.
- """
- ldap = self.obj.backend
-
- group_dn = self.obj.get_dn(*keys, **options)
- user_dn = self.api.Object['user'].get_dn(*keys)
-
- try:
- user_attrs = ldap.get_entry(user_dn)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- is_managed = self.obj.has_objectclass(user_attrs['objectclass'], 'mepmanagedentry')
- if (not ldap.can_write(user_dn, "objectclass") or
- not (ldap.can_write(user_dn, "mepManagedEntry")) and is_managed):
- raise errors.ACIError(info=_('not allowed to modify user entries'))
-
- group_attrs = ldap.get_entry(group_dn)
- is_managed = self.obj.has_objectclass(group_attrs['objectclass'], 'mepmanagedby')
- if (not ldap.can_write(group_dn, "objectclass") or
- not (ldap.can_write(group_dn, "mepManagedBy")) and is_managed):
- raise errors.ACIError(info=_('not allowed to modify group entries'))
-
- objectclasses = user_attrs['objectclass']
- try:
- i = objectclasses.index('mepOriginEntry')
- del objectclasses[i]
- user_attrs['mepManagedEntry'] = None
- ldap.update_entry(user_attrs)
- except ValueError:
- # Somehow the user isn't managed, let it pass for now. We'll
- # let the group throw "Not managed".
- pass
-
- group_attrs = ldap.get_entry(group_dn)
- objectclasses = group_attrs['objectclass']
- try:
- i = objectclasses.index('mepManagedEntry')
- except ValueError:
- # this should never happen
- raise errors.NotFound(reason=_('Not a managed group'))
- del objectclasses[i]
-
- # Make sure the resulting group has the default group objectclasses
- config = ldap.get_ipa_config()
- def_objectclass = config.get(
- self.obj.object_class_config, objectclasses
- )
- objectclasses = list(set(def_objectclass + objectclasses))
-
- group_attrs['mepManagedBy'] = None
- group_attrs['objectclass'] = objectclasses
- ldap.update_entry(group_attrs)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
diff --git a/ipalib/plugins/hbac.py b/ipalib/plugins/hbac.py
deleted file mode 100644
index 59defc1f2..000000000
--- a/ipalib/plugins/hbac.py
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
-#
-
-from ipalib.text import _
-
-__doc__ = _('Host-based access control commands')
diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
deleted file mode 100644
index 7d3e4851a..000000000
--- a/ipalib/plugins/hbacrule.py
+++ /dev/null
@@ -1,605 +0,0 @@
-# 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 ipalib import api, errors
-from ipalib import AccessTime, Str, StrEnum, Bool
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- external_host_param,
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPRetrieve,
- LDAPUpdate,
- LDAPSearch,
- LDAPQuery,
- LDAPAddMember,
- LDAPRemoveMember)
-from ipalib import _, ngettext
-from ipalib import output
-from ipapython.dn import DN
-
-__doc__ = _("""
-Host-based access control
-
-Control who can access what services on what hosts. You
-can use HBAC to control which users or groups can
-access a service, or group of services, on a target host.
-
-You can also specify a category of users and target hosts.
-This is currently limited to "all", but might be expanded in the
-future.
-
-Target hosts in HBAC rules must be hosts managed by IPA.
-
-The available services and groups of services are controlled by the
-hbacsvc and hbacsvcgroup plug-ins respectively.
-
-EXAMPLES:
-
- Create a rule, "test1", that grants all users access to the host "server" from
- anywhere:
- ipa hbacrule-add --usercat=all test1
- ipa hbacrule-add-host --hosts=server.example.com test1
-
- Display the properties of a named HBAC rule:
- ipa hbacrule-show test1
-
- Create a rule for a specific service. This lets the user john access
- the sshd service on any machine from any machine:
- ipa hbacrule-add --hostcat=all john_sshd
- ipa hbacrule-add-user --users=john john_sshd
- ipa hbacrule-add-service --hbacsvcs=sshd john_sshd
-
- Create a rule for a new service group. This lets the user john access
- the FTP service on any machine from any machine:
- ipa hbacsvcgroup-add ftpers
- ipa hbacsvc-add sftp
- ipa hbacsvcgroup-add-member --hbacsvcs=ftp --hbacsvcs=sftp ftpers
- ipa hbacrule-add --hostcat=all john_ftp
- ipa hbacrule-add-user --users=john john_ftp
- ipa hbacrule-add-service --hbacsvcgroups=ftpers john_ftp
-
- Disable a named HBAC rule:
- ipa hbacrule-disable test1
-
- Remove a named HBAC rule:
- ipa hbacrule-del allow_server
-""")
-
-register = Registry()
-
-# AccessTime support is being removed for now.
-#
-# You can also control the times that the rule is active.
-#
-# The access time(s) of a host are cumulative and are not guaranteed to be
-# applied in the order displayed.
-#
-# Specify that the rule "test1" be active every day between 0800 and 1400:
-# ipa hbacrule-add-accesstime --time='periodic daily 0800-1400' test1
-#
-# Specify that the rule "test1" be active once, from 10:32 until 10:33 on
-# December 16, 2010:
-# ipa hbacrule-add-accesstime --time='absolute 201012161032 ~ 201012161033' test1
-
-
-topic = 'hbac'
-
-def validate_type(ugettext, type):
- if type.lower() == 'deny':
- raise errors.ValidationError(name='type', error=_('The deny type has been deprecated.'))
-
-def is_all(options, attribute):
- """
- See if options[attribute] is lower-case 'all' in a safe way.
- """
- if attribute in options and options[attribute] is not None:
- if type(options[attribute]) in (list, tuple):
- value = options[attribute][0].lower()
- else:
- value = options[attribute].lower()
- if value == 'all':
- return True
- else:
- return False
-
-
-@register()
-class hbacrule(LDAPObject):
- """
- HBAC object.
- """
- container_dn = api.env.container_hbac
- object_name = _('HBAC rule')
- object_name_plural = _('HBAC rules')
- object_class = ['ipaassociation', 'ipahbacrule']
- permission_filter_objectclasses = ['ipahbacrule']
- default_attributes = [
- 'cn', 'ipaenabledflag',
- 'description', 'usercategory', 'hostcategory',
- 'servicecategory', 'ipaenabledflag',
- 'memberuser', 'sourcehost', 'memberhost', 'memberservice',
- 'externalhost',
- ]
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- attribute_members = {
- 'memberuser': ['user', 'group'],
- 'memberhost': ['host', 'hostgroup'],
- 'sourcehost': ['host', 'hostgroup'],
- 'memberservice': ['hbacsvc', 'hbacsvcgroup'],
- }
- managed_permissions = {
- 'System: Read HBAC Rules': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'accessruletype', 'accesstime', 'cn', 'description',
- 'externalhost', 'hostcategory', 'ipaenabledflag',
- 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser',
- 'servicecategory', 'sourcehost', 'sourcehostcategory',
- 'usercategory', 'objectclass', 'member',
- },
- },
- 'System: Add HBAC Rule': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC rule";allow (add) groupdn = "ldap:///cn=Add HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Delete HBAC Rule': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC rule";allow (delete) groupdn = "ldap:///cn=Delete HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Manage HBAC Rule Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'externalhost', 'memberhost', 'memberservice', 'memberuser'
- },
- 'replaces': [
- '(targetattr = "memberuser || externalhost || memberservice || memberhost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Manage HBAC rule membership";allow (write) groupdn = "ldap:///cn=Manage HBAC rule membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Modify HBAC Rule': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'accessruletype', 'accesstime', 'cn', 'description',
- 'hostcategory', 'ipaenabledflag', 'servicecategory',
- 'sourcehost', 'sourcehostcategory', 'usercategory'
- },
- 'replaces': [
- '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || accesstime || usercategory || hostcategory || accessruletype || sourcehost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Modify HBAC rule";allow (write) groupdn = "ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- }
-
- label = _('HBAC Rules')
- label_singular = _('HBAC Rule')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Rule name'),
- primary_key=True,
- ),
- StrEnum('accessruletype', validate_type,
- cli_name='type',
- doc=_('Rule type (allow)'),
- label=_('Rule type'),
- values=(u'allow', u'deny'),
- default=u'allow',
- autofill=True,
- exclude='webui',
- flags=['no_option', 'no_output'],
- ),
- # FIXME: {user,host,service}categories should expand in the future
- StrEnum('usercategory?',
- cli_name='usercat',
- label=_('User category'),
- doc=_('User category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('hostcategory?',
- cli_name='hostcat',
- label=_('Host category'),
- doc=_('Host category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('sourcehostcategory?',
- deprecated=True,
- cli_name='srchostcat',
- label=_('Source host category'),
- doc=_('Source host category the rule applies to'),
- values=(u'all', ),
- flags={'no_option'},
- ),
- StrEnum('servicecategory?',
- cli_name='servicecat',
- label=_('Service category'),
- doc=_('Service category the rule applies to'),
- values=(u'all', ),
- ),
-# AccessTime('accesstime?',
-# cli_name='time',
-# label=_('Access time'),
-# ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- Bool('ipaenabledflag?',
- label=_('Enabled'),
- flags=['no_option'],
- ),
- Str('memberuser_user?',
- label=_('Users'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberuser_group?',
- label=_('User Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_host?',
- label=_('Hosts'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_hostgroup?',
- label=_('Host Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('sourcehost_host?',
- deprecated=True,
- label=_('Source Hosts'),
- flags=['no_create', 'no_update', 'no_search', 'no_option'],
- ),
- Str('sourcehost_hostgroup?',
- deprecated=True,
- label=_('Source Host Groups'),
- flags=['no_create', 'no_update', 'no_search', 'no_option'],
- ),
- Str('memberservice_hbacsvc?',
- label=_('Services'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberservice_hbacsvcgroup?',
- label=_('Service Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- external_host_param,
- )
-
-
-
-@register()
-class hbacrule_add(LDAPCreate):
- __doc__ = _('Create a new HBAC rule.')
-
- msg_summary = _('Added HBAC rule "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # HBAC rules are enabled by default
- entry_attrs['ipaenabledflag'] = 'TRUE'
- return dn
-
-
-
-@register()
-class hbacrule_del(LDAPDelete):
- __doc__ = _('Delete an HBAC rule.')
-
- msg_summary = _('Deleted HBAC rule "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- kw = dict(seealso=keys[0])
- _entries = api.Command.selinuxusermap_find(None, **kw)
- if _entries['count']:
- raise errors.DependentEntry(key=keys[0], label=self.api.Object['selinuxusermap'].label_singular, dependent=_entries['result'][0]['cn'][0])
-
- return dn
-
-
-
-@register()
-class hbacrule_mod(LDAPUpdate):
- __doc__ = _('Modify an HBAC rule.')
-
- msg_summary = _('Modified HBAC rule "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, attrs_list)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if is_all(options, 'usercategory') and 'memberuser' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_("user category cannot be set to 'all' while there are allowed users"))
- if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_("host category cannot be set to 'all' while there are allowed hosts"))
- if is_all(options, 'servicecategory') and 'memberservice' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_("service category cannot be set to 'all' while there are allowed services"))
- return dn
-
-
-
-@register()
-class hbacrule_find(LDAPSearch):
- __doc__ = _('Search for HBAC rules.')
-
- msg_summary = ngettext(
- '%(count)d HBAC rule matched', '%(count)d HBAC rules matched', 0
- )
-
-
-
-@register()
-class hbacrule_show(LDAPRetrieve):
- __doc__ = _('Display the properties of an HBAC rule.')
-
-
-
-@register()
-class hbacrule_enable(LDAPQuery):
- __doc__ = _('Enable an HBAC rule.')
-
- msg_summary = _('Enabled HBAC rule "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['TRUE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-
-@register()
-class hbacrule_disable(LDAPQuery):
- __doc__ = _('Disable an HBAC rule.')
-
- msg_summary = _('Disabled HBAC rule "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['FALSE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-# @register()
-class hbacrule_add_accesstime(LDAPQuery):
- """
- Add an access time to an HBAC rule.
- """
-
- takes_options = (
- AccessTime('accesstime',
- cli_name='time',
- label=_('Access time'),
- ),
- )
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
-
- entry_attrs = ldap.get_entry(dn, ['accesstime'])
- entry_attrs.setdefault('accesstime', []).append(
- options['accesstime']
- )
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- return dict(result=True)
-
-
-# @register()
-class hbacrule_remove_accesstime(LDAPQuery):
- """
- Remove access time to HBAC rule.
- """
- takes_options = (
- AccessTime('accesstime?',
- cli_name='time',
- label=_('Access time'),
- ),
- )
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
-
- entry_attrs = ldap.get_entry(dn, ['accesstime'])
- try:
- entry_attrs.setdefault('accesstime', []).remove(
- options['accesstime']
- )
- ldap.update_entry(entry_attrs)
- except (ValueError, errors.EmptyModlist):
- pass
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- return dict(result=True)
-
-
-@register()
-class hbacrule_add_user(LDAPAddMember):
- __doc__ = _('Add users and groups to an HBAC rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'usercategory' in entry_attrs and \
- entry_attrs['usercategory'][0].lower() == 'all':
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when user category='all'"))
- return dn
-
-
-
-@register()
-class hbacrule_remove_user(LDAPRemoveMember):
- __doc__ = _('Remove users and groups from an HBAC rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
-
-
-@register()
-class hbacrule_add_host(LDAPAddMember):
- __doc__ = _('Add target hosts and hostgroups to an HBAC rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'hostcategory' in entry_attrs and \
- entry_attrs['hostcategory'][0].lower() == 'all':
- raise errors.MutuallyExclusiveError(
- reason=_("hosts cannot be added when host category='all'"))
- return dn
-
-
-
-@register()
-class hbacrule_remove_host(LDAPRemoveMember):
- __doc__ = _('Remove target hosts and hostgroups from an HBAC rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
-
-
-@register()
-class hbacrule_add_sourcehost(LDAPAddMember):
- NO_CLI = True
-
- member_attributes = ['sourcehost']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def validate(self, **kw):
- raise errors.DeprecationError(name='hbacrule_add_sourcehost')
-
-
-
-@register()
-class hbacrule_remove_sourcehost(LDAPRemoveMember):
- NO_CLI = True
-
- member_attributes = ['sourcehost']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
- def validate(self, **kw):
- raise errors.DeprecationError(name='hbacrule_remove_sourcehost')
-
-
-
-@register()
-class hbacrule_add_service(LDAPAddMember):
- __doc__ = _('Add services to an HBAC rule.')
-
- member_attributes = ['memberservice']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'servicecategory' in entry_attrs and \
- entry_attrs['servicecategory'][0].lower() == 'all':
- raise errors.MutuallyExclusiveError(reason=_(
- "services cannot be added when service category='all'"))
- return dn
-
-
-
-@register()
-class hbacrule_remove_service(LDAPRemoveMember):
- __doc__ = _('Remove service and service groups from an HBAC rule.')
-
- member_attributes = ['memberservice']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
diff --git a/ipalib/plugins/hbacsvc.py b/ipalib/plugins/hbacsvc.py
deleted file mode 100644
index 43d641642..000000000
--- a/ipalib/plugins/hbacsvc.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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 ipalib import api
-from ipalib import Str
-from ipalib.plugable import Registry
-from .baseldap import LDAPObject, LDAPCreate, LDAPDelete
-from .baseldap import LDAPUpdate, LDAPSearch, LDAPRetrieve
-
-from ipalib import _, ngettext
-
-__doc__ = _("""
-HBAC Services
-
-The PAM services that HBAC can control access to. The name used here
-must match the service name that PAM is evaluating.
-
-EXAMPLES:
-
- Add a new HBAC service:
- ipa hbacsvc-add tftp
-
- Modify an existing HBAC service:
- ipa hbacsvc-mod --desc="TFTP service" tftp
-
- Search for HBAC services. This example will return two results, the FTP
- service and the newly-added tftp service:
- ipa hbacsvc-find ftp
-
- Delete an HBAC service:
- ipa hbacsvc-del tftp
-
-""")
-
-register = Registry()
-
-topic = 'hbac'
-
-@register()
-class hbacsvc(LDAPObject):
- """
- HBAC Service object.
- """
- container_dn = api.env.container_hbacservice
- object_name = _('HBAC service')
- object_name_plural = _('HBAC services')
- object_class = [ 'ipaobject', 'ipahbacservice' ]
- permission_filter_objectclasses = ['ipahbacservice']
- default_attributes = ['cn', 'description', 'memberof']
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'memberof': ['hbacsvcgroup'],
- }
- managed_permissions = {
- 'System: Read HBAC Services': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'ipauniqueid', 'memberof', 'objectclass',
- },
- },
- 'System: Add HBAC Services': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hbacservices,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC services";allow (add) groupdn = "ldap:///cn=Add HBAC services,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Delete HBAC Services': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hbacservices,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC services";allow (delete) groupdn = "ldap:///cn=Delete HBAC services,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- }
-
- label = _('HBAC Services')
- label_singular = _('HBAC Service')
-
- takes_params = (
- Str('cn',
- cli_name='service',
- label=_('Service name'),
- doc=_('HBAC service'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('HBAC service description'),
- ),
- )
-
-
-
-@register()
-class hbacsvc_add(LDAPCreate):
- __doc__ = _('Add a new HBAC service.')
-
- msg_summary = _('Added HBAC service "%(value)s"')
-
-
-
-@register()
-class hbacsvc_del(LDAPDelete):
- __doc__ = _('Delete an existing HBAC service.')
-
- msg_summary = _('Deleted HBAC service "%(value)s"')
-
-
-
-@register()
-class hbacsvc_mod(LDAPUpdate):
- __doc__ = _('Modify an HBAC service.')
-
- msg_summary = _('Modified HBAC service "%(value)s"')
-
-
-
-@register()
-class hbacsvc_find(LDAPSearch):
- __doc__ = _('Search for HBAC services.')
-
- msg_summary = ngettext(
- '%(count)d HBAC service matched', '%(count)d HBAC services matched', 0
- )
-
-
-
-@register()
-class hbacsvc_show(LDAPRetrieve):
- __doc__ = _('Display information about an HBAC service.')
-
diff --git a/ipalib/plugins/hbacsvcgroup.py b/ipalib/plugins/hbacsvcgroup.py
deleted file mode 100644
index 41157efc6..000000000
--- a/ipalib/plugins/hbacsvcgroup.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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 ipalib import api, Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPUpdate,
- LDAPRetrieve,
- LDAPSearch,
- LDAPDelete,
- LDAPAddMember,
- LDAPRemoveMember)
-from ipalib import _, ngettext
-
-__doc__ = _("""
-HBAC Service Groups
-
-HBAC service groups can contain any number of individual services,
-or "members". Every group must have a description.
-
-EXAMPLES:
-
- Add a new HBAC service group:
- ipa hbacsvcgroup-add --desc="login services" login
-
- Add members to an HBAC service group:
- ipa hbacsvcgroup-add-member --hbacsvcs=sshd --hbacsvcs=login login
-
- Display information about a named group:
- ipa hbacsvcgroup-show login
-
- Delete an HBAC service group:
- ipa hbacsvcgroup-del login
-""")
-
-register = Registry()
-
-topic = 'hbac'
-
-@register()
-class hbacsvcgroup(LDAPObject):
- """
- HBAC service group object.
- """
- container_dn = api.env.container_hbacservicegroup
- object_name = _('HBAC service group')
- object_name_plural = _('HBAC service groups')
- object_class = ['ipaobject', 'ipahbacservicegroup']
- permission_filter_objectclasses = ['ipahbacservicegroup']
- default_attributes = [ 'cn', 'description', 'member' ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'member': ['hbacsvc'],
- }
- managed_permissions = {
- 'System: Read HBAC Service Groups': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'ipauniqueid',
- 'member', 'o', 'objectclass', 'ou', 'owner', 'seealso',
- 'memberuser', 'memberhost',
- },
- },
- 'System: Add HBAC Service Groups': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hbacservicegroups,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC service groups";allow (add) groupdn = "ldap:///cn=Add HBAC service groups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Delete HBAC Service Groups': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hbacservicegroups,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC service groups";allow (delete) groupdn = "ldap:///cn=Delete HBAC service groups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- 'System: Manage HBAC Service Group Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=hbacservicegroups,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Manage HBAC service group membership";allow (write) groupdn = "ldap:///cn=Manage HBAC service group membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'HBAC Administrator'},
- },
- }
-
- label = _('HBAC Service Groups')
- label_singular = _('HBAC Service Group')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Service group name'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('HBAC service group description'),
- ),
- )
-
-
-
-@register()
-class hbacsvcgroup_add(LDAPCreate):
- __doc__ = _('Add a new HBAC service group.')
-
- msg_summary = _('Added HBAC service group "%(value)s"')
-
-
-
-@register()
-class hbacsvcgroup_del(LDAPDelete):
- __doc__ = _('Delete an HBAC service group.')
-
- msg_summary = _('Deleted HBAC service group "%(value)s"')
-
-
-
-@register()
-class hbacsvcgroup_mod(LDAPUpdate):
- __doc__ = _('Modify an HBAC service group.')
-
- msg_summary = _('Modified HBAC service group "%(value)s"')
-
-
-
-@register()
-class hbacsvcgroup_find(LDAPSearch):
- __doc__ = _('Search for an HBAC service group.')
-
- msg_summary = ngettext(
- '%(count)d HBAC service group matched', '%(count)d HBAC service groups matched', 0
- )
-
-
-
-@register()
-class hbacsvcgroup_show(LDAPRetrieve):
- __doc__ = _('Display information about an HBAC service group.')
-
-
-
-@register()
-class hbacsvcgroup_add_member(LDAPAddMember):
- __doc__ = _('Add members to an HBAC service group.')
-
-
-
-@register()
-class hbacsvcgroup_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from an HBAC service group.')
-
diff --git a/ipalib/plugins/hbactest.py b/ipalib/plugins/hbactest.py
deleted file mode 100644
index 90f3b561a..000000000
--- a/ipalib/plugins/hbactest.py
+++ /dev/null
@@ -1,499 +0,0 @@
-# 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 ipalib import api, errors, output, util
-from ipalib import Command, Str, Flag, Int
-from ipalib import _
-from ipapython.dn import DN
-from ipalib.plugable import Registry
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- import ipaserver.dcerpc
- _dcerpc_bindings_installed = True
- except ImportError:
- _dcerpc_bindings_installed = False
-
-import pyhbac
-import six
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Simulate use of Host-based access controls
-
-HBAC rules control who can access what services on what hosts.
-You can use HBAC to control which users or groups can access a service,
-or group of services, on a target host.
-
-Since applying HBAC rules implies use of a production environment,
-this plugin aims to provide simulation of HBAC rules evaluation without
-having access to the production environment.
-
- Test user coming to a service on a named host against
- existing enabled rules.
-
- ipa hbactest --user= --host= --service=
- [--rules=rules-list] [--nodetail] [--enabled] [--disabled]
- [--sizelimit= ]
-
- --user, --host, and --service are mandatory, others are optional.
-
- If --rules is specified simulate enabling of the specified rules and test
- the login of the user using only these rules.
-
- If --enabled is specified, all enabled HBAC rules will be added to simulation
-
- If --disabled is specified, all disabled HBAC rules will be added to simulation
-
- If --nodetail is specified, do not return information about rules matched/not matched.
-
- If both --rules and --enabled are specified, apply simulation to --rules _and_
- all IPA enabled rules.
-
- If no --rules specified, simulation is run against all IPA enabled rules.
- By default there is a IPA-wide limit to number of entries fetched, you can change it
- with --sizelimit option.
-
-EXAMPLES:
-
- 1. Use all enabled HBAC rules in IPA database to simulate:
- $ ipa hbactest --user=a1a --host=bar --service=sshd
- --------------------
- Access granted: True
- --------------------
- Not matched rules: my-second-rule
- Not matched rules: my-third-rule
- Not matched rules: myrule
- Matched rules: allow_all
-
- 2. Disable detailed summary of how rules were applied:
- $ ipa hbactest --user=a1a --host=bar --service=sshd --nodetail
- --------------------
- Access granted: True
- --------------------
-
- 3. Test explicitly specified HBAC rules:
- $ ipa hbactest --user=a1a --host=bar --service=sshd \\
- --rules=myrule --rules=my-second-rule
- ---------------------
- Access granted: False
- ---------------------
- Not matched rules: my-second-rule
- Not matched rules: myrule
-
- 4. Use all enabled HBAC rules in IPA database + explicitly specified rules:
- $ ipa hbactest --user=a1a --host=bar --service=sshd \\
- --rules=myrule --rules=my-second-rule --enabled
- --------------------
- Access granted: True
- --------------------
- Not matched rules: my-second-rule
- Not matched rules: my-third-rule
- Not matched rules: myrule
- Matched rules: allow_all
-
- 5. Test all disabled HBAC rules in IPA database:
- $ ipa hbactest --user=a1a --host=bar --service=sshd --disabled
- ---------------------
- Access granted: False
- ---------------------
- Not matched rules: new-rule
-
- 6. Test all disabled HBAC rules in IPA database + explicitly specified rules:
- $ ipa hbactest --user=a1a --host=bar --service=sshd \\
- --rules=myrule --rules=my-second-rule --disabled
- ---------------------
- Access granted: False
- ---------------------
- Not matched rules: my-second-rule
- Not matched rules: my-third-rule
- Not matched rules: myrule
-
- 7. Test all (enabled and disabled) HBAC rules in IPA database:
- $ ipa hbactest --user=a1a --host=bar --service=sshd \\
- --enabled --disabled
- --------------------
- Access granted: True
- --------------------
- Not matched rules: my-second-rule
- Not matched rules: my-third-rule
- Not matched rules: myrule
- Not matched rules: new-rule
- Matched rules: allow_all
-
-
-HBACTEST AND TRUSTED DOMAINS
-
-When an external trusted domain is configured in IPA, HBAC rules are also applied
-on users accessing IPA resources from the trusted domain. Trusted domain users and
-groups (and their SIDs) can be then assigned to external groups which can be
-members of POSIX groups in IPA which can be used in HBAC rules and thus allowing
-access to resources protected by the HBAC system.
-
-hbactest plugin is capable of testing access for both local IPA users and users
-from the trusted domains, either by a fully qualified user name or by user SID.
-Such user names need to have a trusted domain specified as a short name
-(DOMAIN\Administrator) or with a user principal name (UPN), Administrator@ad.test.
-
-Please note that hbactest executed with a trusted domain user as --user parameter
-can be only run by members of "trust admins" group.
-
-EXAMPLES:
-
- 1. Test if a user from a trusted domain specified by its shortname matches any
- rule:
-
- $ ipa hbactest --user 'DOMAIN\Administrator' --host `hostname` --service sshd
- --------------------
- Access granted: True
- --------------------
- Matched rules: allow_all
- Matched rules: can_login
-
- 2. Test if a user from a trusted domain specified by its domain name matches
- any rule:
-
- $ ipa hbactest --user 'Administrator@domain.com' --host `hostname` --service sshd
- --------------------
- Access granted: True
- --------------------
- Matched rules: allow_all
- Matched rules: can_login
-
- 3. Test if a user from a trusted domain specified by its SID matches any rule:
-
- $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-500 \\
- --host `hostname` --service sshd
- --------------------
- Access granted: True
- --------------------
- Matched rules: allow_all
- Matched rules: can_login
-
- 4. Test if other user from a trusted domain specified by its SID matches any rule:
-
- $ ipa hbactest --user S-1-5-21-3035198329-144811719-1378114514-1203 \\
- --host `hostname` --service sshd
- --------------------
- Access granted: True
- --------------------
- Matched rules: allow_all
- Not matched rules: can_login
-
- 5. Test if other user from a trusted domain specified by its shortname matches
- any rule:
-
- $ ipa hbactest --user 'DOMAIN\Otheruser' --host `hostname` --service sshd
- --------------------
- Access granted: True
- --------------------
- Matched rules: allow_all
- Not matched rules: can_login
-""")
-
-register = Registry()
-
-def convert_to_ipa_rule(rule):
- # convert a dict with a rule to an pyhbac rule
- ipa_rule = pyhbac.HbacRule(rule['cn'][0])
- ipa_rule.enabled = rule['ipaenabledflag'][0]
- # Following code attempts to process rule systematically
- structure = \
- (('user', 'memberuser', 'user', 'group', ipa_rule.users),
- ('host', 'memberhost', 'host', 'hostgroup', ipa_rule.targethosts),
- ('sourcehost', 'sourcehost', 'host', 'hostgroup', ipa_rule.srchosts),
- ('service', 'memberservice', 'hbacsvc', 'hbacsvcgroup', ipa_rule.services),
- )
- for element in structure:
- category = '%scategory' % (element[0])
- if (category in rule and rule[category][0] == u'all') or (element[0] == 'sourcehost'):
- # rule applies to all elements
- # sourcehost is always set to 'all'
- element[4].category = set([pyhbac.HBAC_CATEGORY_ALL])
- else:
- # rule is about specific entities
- # Check if there are explicitly listed entities
- attr_name = '%s_%s' % (element[1], element[2])
- if attr_name in rule:
- element[4].names = rule[attr_name]
- # Now add groups of entities if they are there
- attr_name = '%s_%s' % (element[1], element[3])
- if attr_name in rule:
- element[4].groups = rule[attr_name]
- if 'externalhost' in rule:
- ipa_rule.srchosts.names.extend(rule['externalhost']) #pylint: disable=E1101
- return ipa_rule
-
-
-@register()
-class hbactest(Command):
- __doc__ = _('Simulate use of Host-based access controls')
-
- has_output = (
- output.summary,
- output.Output('warning', (list, tuple, type(None)), _('Warning')),
- output.Output('matched', (list, tuple, type(None)), _('Matched rules')),
- output.Output('notmatched', (list, tuple, type(None)), _('Not matched rules')),
- output.Output('error', (list, tuple, type(None)), _('Non-existent or invalid rules')),
- output.Output('value', bool, _('Result of simulation'), ['no_display']),
- )
-
- takes_options = (
- Str('user',
- cli_name='user',
- label=_('User name'),
- primary_key=True,
- ),
- Str('sourcehost?',
- deprecated=True,
- cli_name='srchost',
- label=_('Source host'),
- flags={'no_option'},
- ),
- Str('targethost',
- cli_name='host',
- label=_('Target host'),
- ),
- Str('service',
- cli_name='service',
- label=_('Service'),
- ),
- Str('rules*',
- cli_name='rules',
- label=_('Rules to test. If not specified, --enabled is assumed'),
- ),
- Flag('nodetail?',
- cli_name='nodetail',
- label=_('Hide details which rules are matched, not matched, or invalid'),
- ),
- Flag('enabled?',
- cli_name='enabled',
- label=_('Include all enabled IPA rules into test [default]'),
- ),
- Flag('disabled?',
- cli_name='disabled',
- label=_('Include all disabled IPA rules into test'),
- ),
- Int('sizelimit?',
- label=_('Size Limit'),
- doc=_('Maximum number of rules to process when no --rules is specified'),
- flags=['no_display'],
- minvalue=0,
- autofill=False,
- ),
- )
-
- def canonicalize(self, host):
- """
- Canonicalize the host name -- add default IPA domain if that is missing
- """
- if host.find('.') == -1:
- return u'%s.%s' % (host, self.env.domain)
- return host
-
- def execute(self, *args, **options):
- # First receive all needed information:
- # 1. HBAC rules (whether enabled or disabled)
- # 2. Required options are (user, target host, service)
- # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output
- rules = []
-
- # Use all enabled IPA rules by default
- all_enabled = True
- all_disabled = False
-
- # We need a local copy of test rules in order find incorrect ones
- testrules = {}
- if 'rules' in options:
- testrules = list(options['rules'])
- # When explicit rules are provided, disable assumptions
- all_enabled = False
- all_disabled = False
-
- sizelimit = None
- if 'sizelimit' in options:
- sizelimit = int(options['sizelimit'])
-
- # Check if --disabled is specified, include all disabled IPA rules
- if options['disabled']:
- all_disabled = True
- all_enabled = False
-
- # Finally, if enabled is specified implicitly, override above decisions
- if options['enabled']:
- all_enabled = True
-
- hbacset = []
- if len(testrules) == 0:
- hbacset = self.api.Command.hbacrule_find(
- sizelimit=sizelimit, no_members=False)['result']
- else:
- for rule in testrules:
- try:
- hbacset.append(self.api.Command.hbacrule_show(rule)['result'])
- except Exception:
- pass
-
- # We have some rules, import them
- # --enabled will import all enabled rules (default)
- # --disabled will import all disabled rules
- # --rules will implicitly add the rules from a rule list
- for rule in hbacset:
- ipa_rule = convert_to_ipa_rule(rule)
- if ipa_rule.name in testrules:
- ipa_rule.enabled = True
- rules.append(ipa_rule)
- testrules.remove(ipa_rule.name)
- elif all_enabled and ipa_rule.enabled:
- # Option --enabled forces to include all enabled IPA rules into test
- rules.append(ipa_rule)
- elif all_disabled and not ipa_rule.enabled:
- # Option --disabled forces to include all disabled IPA rules into test
- ipa_rule.enabled = True
- rules.append(ipa_rule)
-
- # Check if there are unresolved rules left
- if len(testrules) > 0:
- # Error, unresolved rules are left in --rules
- return {'summary' : unicode(_(u'Unresolved rules in --rules')),
- 'error': testrules, 'matched': None, 'notmatched': None,
- 'warning' : None, 'value' : False}
-
- # Rules are converted to pyhbac format, build request and then test it
- request = pyhbac.HbacRequest()
-
- if options['user'] != u'all':
- # check first if this is not a trusted domain user
- if _dcerpc_bindings_installed:
- is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user'])
- else:
- is_valid_sid = False
- components = util.normalize_name(options['user'])
- if is_valid_sid or 'domain' in components or 'flatname' in components:
- # this is a trusted domain user
- if not _dcerpc_bindings_installed:
- raise errors.NotFound(reason=_(
- 'Cannot perform external member validation without '
- 'Samba 4 support installed. Make sure you have installed '
- 'server-trust-ad sub-package of IPA on the server'))
- domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
- if not domain_validator.is_configured():
- raise errors.NotFound(reason=_(
- 'Cannot search in trusted domains without own domain configured. '
- 'Make sure you have run ipa-adtrust-install on the IPA server first'))
- user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups(options['user'])
- request.user.name = user_sid
-
- # Now search for all external groups that have this user or
- # any of its groups in its external members. Found entires
- # memberOf links will be then used to gather all groups where
- # this group is assigned, including the nested ones
- filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \
- % ")(ipaExternalMember=".join(group_sids + [user_sid])
-
- ldap = self.api.Backend.ldap2
- group_container = DN(api.env.container_group, api.env.basedn)
- try:
- entries, truncated = ldap.find_entries(filter_sids, ['memberof'], group_container)
- except errors.NotFound:
- request.user.groups = []
- else:
- groups = []
- for entry in entries:
- memberof_dns = entry.get('memberof', [])
- for memberof_dn in memberof_dns:
- if memberof_dn.endswith(group_container):
- groups.append(memberof_dn[0][0].value)
- request.user.groups = sorted(set(groups))
- else:
- # try searching for a local user
- try:
- request.user.name = options['user']
- search_result = self.api.Command.user_show(request.user.name)['result']
- groups = search_result['memberof_group']
- if 'memberofindirect_group' in search_result:
- groups += search_result['memberofindirect_group']
- request.user.groups = sorted(set(groups))
- except Exception:
- pass
-
- if options['service'] != u'all':
- try:
- request.service.name = options['service']
- service_result = self.api.Command.hbacsvc_show(request.service.name)['result']
- if 'memberof_hbacsvcgroup' in service_result:
- request.service.groups = service_result['memberof_hbacsvcgroup']
- except Exception:
- pass
-
- if options['targethost'] != u'all':
- try:
- request.targethost.name = self.canonicalize(options['targethost'])
- tgthost_result = self.api.Command.host_show(request.targethost.name)['result']
- groups = tgthost_result['memberof_hostgroup']
- if 'memberofindirect_hostgroup' in tgthost_result:
- groups += tgthost_result['memberofindirect_hostgroup']
- request.targethost.groups = sorted(set(groups))
- except Exception:
- pass
-
- matched_rules = []
- notmatched_rules = []
- error_rules = []
- warning_rules = []
-
- result = {'warning':None, 'matched':None, 'notmatched':None, 'error':None}
- if not options['nodetail']:
- # Validate runs rules one-by-one and reports failed ones
- for ipa_rule in rules:
- try:
- res = request.evaluate([ipa_rule])
- if res == pyhbac.HBAC_EVAL_ALLOW:
- matched_rules.append(ipa_rule.name)
- if res == pyhbac.HBAC_EVAL_DENY:
- notmatched_rules.append(ipa_rule.name)
- except pyhbac.HbacError as e:
- code, rule_name = e.args
- if code == pyhbac.HBAC_EVAL_ERROR:
- error_rules.append(rule_name)
- self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \
- (rule_name, pyhbac.hbac_result_string(code)))
- except (TypeError, IOError) as info:
- self.log.error('Native IPA HBAC module error: %s' % info)
-
- access_granted = len(matched_rules) > 0
- else:
- res = request.evaluate(rules)
- access_granted = (res == pyhbac.HBAC_EVAL_ALLOW)
-
- result['summary'] = _('Access granted: %s') % (access_granted)
-
-
- if len(matched_rules) > 0:
- result['matched'] = matched_rules
- if len(notmatched_rules) > 0:
- result['notmatched'] = notmatched_rules
- if len(error_rules) > 0:
- result['error'] = error_rules
- if len(warning_rules) > 0:
- result['warning'] = warning_rules
-
- result['value'] = access_granted
- return result
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
deleted file mode 100644
index 709b78d5b..000000000
--- a/ipalib/plugins/host.py
+++ /dev/null
@@ -1,1284 +0,0 @@
-# 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 string
-
-import six
-
-from ipalib import api, errors, util
-from ipalib import messages
-from ipalib import Str, Flag, Bytes
-from ipalib.plugable import Registry
-from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
- LDAPDelete, LDAPUpdate, LDAPSearch,
- LDAPRetrieve, LDAPAddMember,
- LDAPRemoveMember, host_is_master,
- pkey_to_value, add_missing_object_class,
- LDAPAddAttribute, LDAPRemoveAttribute)
-from .service import (split_principal, validate_certificate,
- set_certificate_attrs, ticket_flags_params, update_krbticketflags,
- set_kerberos_attrs, rename_ipaallowedtoperform_from_ldap,
- rename_ipaallowedtoperform_to_ldap, revoke_certs)
-from .dns import (dns_container_exists,
- add_records_for_host_validation, add_records_for_host,
- get_reverse_zone)
-from ipalib import _, ngettext
-from ipalib import x509
-from ipalib import output
-from ipalib.request import context
-from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options,
- convert_sshpubkey_post, validate_hostname,
- add_sshpubkey_to_attrs_pre,
- remove_sshpubkey_from_output_post,
- remove_sshpubkey_from_output_list_post)
-from ipapython.ipautil import ipa_generate_password, CheckedIPAddress
-from ipapython.dnsutil import DNSName
-from ipapython.ssh import SSHPublicKey
-from ipapython.dn import DN
-from functools import reduce
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Hosts/Machines
-
-A host represents a machine. It can be used in a number of contexts:
-- service entries are associated with a host
-- a host stores the host/ service principal
-- a host can be used in Host-based Access Control (HBAC) rules
-- every enrolled client generates a host entry
-""") + _("""
-ENROLLMENT:
-
-There are three enrollment scenarios when enrolling a new client:
-
-1. You are enrolling as a full administrator. The host entry may exist
- or not. A full administrator is a member of the hostadmin role
- or the admins group.
-2. You are enrolling as a limited administrator. The host must already
- exist. A limited administrator is a member a role with the
- Host Enrollment privilege.
-3. The host has been created with a one-time password.
-""") + _("""
-RE-ENROLLMENT:
-
-Host that has been enrolled at some point, and lost its configuration (e.g. VM
-destroyed) can be re-enrolled.
-
-For more information, consult the manual pages for ipa-client-install.
-
-A host can optionally store information such as where it is located,
-the OS that it runs, etc.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Add a new host:
- ipa host-add --location="3rd floor lab" --locality=Dallas test.example.com
-""") + _("""
- Delete a host:
- ipa host-del test.example.com
-""") + _("""
- Add a new host with a one-time password:
- ipa host-add --os='Fedora 12' --password=Secret123 test.example.com
-""") + _("""
- Add a new host with a random one-time password:
- ipa host-add --os='Fedora 12' --random test.example.com
-""") + _("""
- Modify information about a host:
- ipa host-mod --os='Fedora 12' test.example.com
-""") + _("""
- Remove SSH public keys of a host and update DNS to reflect this change:
- ipa host-mod --sshpubkey= --updatedns test.example.com
-""") + _("""
- Disable the host Kerberos key, SSL certificate and all of its services:
- ipa host-disable test.example.com
-""") + _("""
- Add a host that can manage this host's keytab and certificate:
- ipa host-add-managedby --hosts=test2 test
-""") + _("""
- Allow user to create a keytab:
- ipa host-allow-create-keytab test2 --users=tuser1
-""")
-
-register = Registry()
-
-# Characters to be used by random password generator
-# The set was chosen to avoid the need for escaping the characters by user
-host_pwd_chars = string.digits + string.ascii_letters + '_,.@+-='
-
-
-def remove_ptr_rec(ipaddr, host, domain):
- """
- Remove PTR record of IP address (ipaddr)
- :return: True if PTR record was removed, False if record was not found
- """
- api.log.debug('deleting PTR record of ipaddr %s', ipaddr)
- try:
- revzone, revname = get_reverse_zone(ipaddr)
-
- # in case domain is in FQDN form with a trailing dot, we needn't add
- # another one, in case it has no trailing dot, dnsrecord-del will
- # normalize the entry
- delkw = {'ptrrecord': "%s.%s" % (host, domain)}
-
- api.Command['dnsrecord_del'](revzone, revname, **delkw)
- except errors.NotFound:
- api.log.debug('PTR record of ipaddr %s not found', ipaddr)
- return False
-
- return True
-
-
-def update_sshfp_record(zone, record, entry_attrs):
- if 'ipasshpubkey' not in entry_attrs:
- return
-
- pubkeys = entry_attrs['ipasshpubkey'] or ()
- sshfps = []
- for pubkey in pubkeys:
- try:
- sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha1()
- except (ValueError, UnicodeDecodeError):
- continue
- if sshfp is not None:
- sshfps.append(sshfp)
-
- try:
- sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha256()
- except (ValueError, UnicodeDecodeError):
- continue
- if sshfp is not None:
- sshfps.append(sshfp)
-
- try:
- api.Command['dnsrecord_mod'](zone, record, sshfprecord=sshfps)
- except errors.EmptyModlist:
- pass
-
-
-def convert_ipaassignedidview_post(entry_attrs, options):
- """
- Converts the ID View DN to its name for the better looking output.
- """
-
- if 'ipaassignedidview' in entry_attrs and not options.get('raw'):
- idview_name = entry_attrs.single_value['ipaassignedidview'][0].value
- entry_attrs.single_value['ipaassignedidview'] = idview_name
-
-
-host_output_params = (
- Flag('has_keytab',
- label=_('Keytab'),
- ),
- Str('managedby_host',
- label='Managed by',
- ),
- Str('managing_host',
- label='Managing',
- ),
- Str('subject',
- label=_('Subject'),
- ),
- Str('serial_number',
- label=_('Serial Number'),
- ),
- Str('serial_number_hex',
- label=_('Serial Number (hex)'),
- ),
- Str('issuer',
- label=_('Issuer'),
- ),
- Str('valid_not_before',
- label=_('Not Before'),
- ),
- Str('valid_not_after',
- label=_('Not After'),
- ),
- Str('md5_fingerprint',
- label=_('Fingerprint (MD5)'),
- ),
- Str('sha1_fingerprint',
- label=_('Fingerprint (SHA1)'),
- ),
- Str('revocation_reason?',
- label=_('Revocation reason'),
- ),
- Str('managedby',
- label=_('Failed managedby'),
- ),
- Str('sshpubkeyfp*',
- label=_('SSH public key fingerprint'),
- ),
- Str('ipaallowedtoperform_read_keys_user',
- label=_('Users allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_group',
- label=_('Groups allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_host',
- label=_('Hosts allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_hostgroup',
- label=_('Host Groups allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_user',
- label=_('Users allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_group',
- label=_('Groups allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_host',
- label=_('Hosts allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_hostgroup',
- label=_('Host Groups allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_read_keys',
- label=_('Failed allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_write_keys',
- label=_('Failed allowed to create keytab'),
- ),
-)
-
-
-def validate_ipaddr(ugettext, ipaddr):
- """
- Verify that we have either an IPv4 or IPv6 address.
- """
- try:
- CheckedIPAddress(ipaddr, match_local=False)
- except Exception as e:
- return unicode(e)
- return None
-
-
-def normalize_hostname(hostname):
- """Use common fqdn form without the trailing dot"""
- if hostname.endswith(u'.'):
- hostname = hostname[:-1]
- hostname = hostname.lower()
- return hostname
-
-
-def _hostname_validator(ugettext, value):
- try:
- validate_hostname(value)
- except ValueError as e:
- return _('invalid domain-name: %s') % unicode(e)
-
- return None
-
-
-@register()
-class host(LDAPObject):
- """
- Host object.
- """
- container_dn = api.env.container_host
- object_name = _('host')
- object_name_plural = _('hosts')
- object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser', 'ipaservice']
- possible_objectclasses = ['ipaallowedoperations']
- permission_filter_objectclasses = ['ipahost']
- # object_class_config = 'ipahostobjectclasses'
- search_attributes = [
- 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname',
- 'nshardwareplatform', 'nsosversion', 'managedby',
- ]
- default_attributes = [
- 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname',
- 'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof',
- 'managedby', 'memberofindirect', 'macaddress',
- 'userclass', 'ipaallowedtoperform', 'ipaassignedidview',
- ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'enrolledby': ['user'],
- 'memberof': ['hostgroup', 'netgroup', 'role', 'hbacrule', 'sudorule'],
- 'managedby': ['host'],
- 'managing': ['host'],
- 'memberofindirect': ['hostgroup', 'netgroup', 'role', 'hbacrule',
- 'sudorule'],
- 'ipaallowedtoperform_read_keys': ['user', 'group', 'host', 'hostgroup'],
- 'ipaallowedtoperform_write_keys': ['user', 'group', 'host', 'hostgroup'],
- }
- bindable = True
- relationships = {
- 'memberof': ('Member Of', 'in_', 'not_in_'),
- 'enrolledby': ('Enrolled by', 'enroll_by_', 'not_enroll_by_'),
- 'managedby': ('Managed by', 'man_by_', 'not_man_by_'),
- 'managing': ('Managing', 'man_', 'not_man_'),
- 'ipaallowedtoperform_read_keys': ('Allow to retrieve keytab by', 'retrieve_keytab_by_', 'not_retrieve_keytab_by_'),
- 'ipaallowedtoperform_write_keys': ('Allow to create keytab by', 'write_keytab_by_', 'not_write_keytab_by'),
- }
- password_attributes = [('userpassword', 'has_password'),
- ('krbprincipalkey', 'has_keytab')]
- managed_permissions = {
- 'System: Read Hosts': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'fqdn', 'ipaclientversion',
- 'ipakrbauthzdata', 'ipasshpubkey', 'ipauniqueid',
- 'krbprincipalname', 'l', 'macaddress', 'nshardwareplatform',
- 'nshostlocation', 'nsosversion', 'objectclass',
- 'serverhostname', 'usercertificate', 'userclass',
- 'enrolledby', 'managedby', 'ipaassignedidview',
- 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases',
- 'krbprincipalexpiration', 'krbpasswordexpiration',
- 'krblastpwdchange',
- },
- },
- 'System: Read Host Membership': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'memberof',
- },
- },
- 'System: Add Hosts': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Hosts";allow (add) groupdn = "ldap:///cn=Add Hosts,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators'},
- },
- 'System: Add krbPrincipalName to a Host': {
- # Allow an admin to enroll a host that has a one-time password.
- # When a host is created with a password no krbPrincipalName is set.
- # This will let it be added if the client ends up enrolling with
- # an administrator instead.
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': [
- '(objectclass=ipahost)',
- '(!(krbprincipalname=*))',
- ],
- 'ipapermdefaultattr': {'krbprincipalname'},
- 'replaces': [
- '(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(targetfilter = "(!(krbprincipalname=*))")(targetattr = "krbprincipalname")(version 3.0;acl "permission:Add krbPrincipalName to a host"; allow (write) groupdn = "ldap:///cn=Add krbPrincipalName to a host,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators', 'Host Enrollment'},
- },
- 'System: Enroll a Host': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'objectclass', 'enrolledby'},
- 'replaces': [
- '(targetattr = "objectclass")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Enroll a host";allow (write) groupdn = "ldap:///cn=Enroll a host,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetattr = "enrolledby || objectclass")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Enroll a host";allow (write) groupdn = "ldap:///cn=Enroll a host,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators', 'Host Enrollment'},
- },
- 'System: Manage Host SSH Public Keys': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'ipasshpubkey'},
- 'replaces': [
- '(targetattr = "ipasshpubkey")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage Host SSH Public Keys";allow (write) groupdn = "ldap:///cn=Manage Host SSH Public Keys,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators'},
- },
- 'System: Manage Host Keytab': {
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': [
- '(objectclass=ipahost)',
- '(!(memberOf=%s))' % DN('cn=ipaservers',
- api.env.container_hostgroup,
- api.env.basedn),
- ],
- 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'},
- 'replaces': [
- '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage host keytab";allow (write) groupdn = "ldap:///cn=Manage host keytab,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators', 'Host Enrollment'},
- },
- 'System: Manage Host Keytab Permissions': {
- 'ipapermright': {'read', 'search', 'compare', 'write'},
- 'ipapermdefaultattr': {
- 'ipaallowedtoperform;write_keys',
- 'ipaallowedtoperform;read_keys', 'objectclass'
- },
- 'default_privileges': {'Host Administrators'},
- },
- 'System: Modify Hosts': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'description', 'l', 'nshardwareplatform', 'nshostlocation',
- 'nsosversion', 'macaddress', 'userclass', 'ipaassignedidview',
- },
- 'replaces': [
- '(targetattr = "description || l || nshostlocation || nshardwareplatform || nsosversion")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Hosts";allow (write) groupdn = "ldap:///cn=Modify Hosts,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators'},
- },
- 'System: Remove Hosts': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Hosts";allow (delete) groupdn = "ldap:///cn=Remove Hosts,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Administrators'},
- },
- 'System: Manage Host Certificates': {
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'usercertificate'},
- 'default_privileges': {'Host Administrators', 'Host Enrollment'},
- },
- 'System: Manage Host Enrollment Password': {
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'userpassword'},
- 'default_privileges': {'Host Administrators', 'Host Enrollment'},
- },
- 'System: Read Host Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=computers', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'macaddress',
- },
- },
- }
-
- label = _('Hosts')
- label_singular = _('Host')
-
- takes_params = (
- Str('fqdn', _hostname_validator,
- cli_name='hostname',
- label=_('Host name'),
- primary_key=True,
- normalizer=normalize_hostname,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this host'),
- ),
- Str('l?',
- cli_name='locality',
- label=_('Locality'),
- doc=_('Host locality (e.g. "Baltimore, MD")'),
- ),
- Str('nshostlocation?',
- cli_name='location',
- label=_('Location'),
- doc=_('Host location (e.g. "Lab 2")'),
- ),
- Str('nshardwareplatform?',
- cli_name='platform',
- label=_('Platform'),
- doc=_('Host hardware platform (e.g. "Lenovo T61")'),
- ),
- Str('nsosversion?',
- cli_name='os',
- label=_('Operating system'),
- doc=_('Host operating system and version (e.g. "Fedora 9")'),
- ),
- Str('userpassword?',
- cli_name='password',
- label=_('User password'),
- doc=_('Password used in bulk enrollment'),
- ),
- Flag('random?',
- doc=_('Generate a random password to be used in bulk enrollment'),
- flags=('no_search', 'virtual_attribute'),
- default=False,
- ),
- Str('randompassword?',
- label=_('Random password'),
- flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
- ),
- Bytes('usercertificate*', validate_certificate,
- cli_name='certificate',
- label=_('Certificate'),
- doc=_('Base-64 encoded host certificate'),
- ),
- Str('krbprincipalname?',
- label=_('Principal name'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('macaddress*',
- normalizer=lambda value: value.upper(),
- pattern='^([a-fA-F0-9]{2}[:|\-]?){5}[a-fA-F0-9]{2}$',
- pattern_errmsg=('Must be of the form HH:HH:HH:HH:HH:HH, where '
- 'each H is a hexadecimal character.'),
- label=_('MAC address'),
- doc=_('Hardware MAC address(es) on this host'),
- ),
- Str('ipasshpubkey*', validate_sshpubkey_no_options,
- cli_name='sshpubkey',
- label=_('SSH public key'),
- normalizer=normalize_sshpubkey,
- flags=['no_search'],
- ),
- Str('userclass*',
- cli_name='class',
- label=_('Class'),
- doc=_('Host category (semantics placed on this attribute are for '
- 'local interpretation)'),
- ),
- Str('ipaassignedidview?',
- label=_('Assigned ID View'),
- flags=['no_option'],
- ),
- ) + ticket_flags_params
-
- def get_dn(self, *keys, **options):
- hostname = keys[-1]
- dn = super(host, self).get_dn(hostname, **options)
- try:
- self.backend.get_entry(dn, [''])
- except errors.NotFound:
- try:
- entry_attrs = self.backend.find_entry_by_attr(
- 'serverhostname', hostname, self.object_class, [''],
- DN(self.container_dn, api.env.basedn))
- dn = entry_attrs.dn
- except errors.NotFound:
- pass
- return dn
-
- def get_managed_hosts(self, dn):
- host_filter = 'managedBy=%s' % dn
- host_attrs = ['fqdn']
- ldap = self.api.Backend.ldap2
- managed_hosts = []
-
- try:
- (hosts, truncated) = ldap.find_entries(
- base_dn=DN(self.container_dn, api.env.basedn),
- filter=host_filter, attrs_list=host_attrs)
-
- for host in hosts:
- managed_hosts.append(host.dn)
- except errors.NotFound:
- return []
-
- return managed_hosts
-
- def suppress_netgroup_memberof(self, ldap, entry_attrs):
- """
- We don't want to show managed netgroups so remove them from the
- memberofindirect list.
- """
- ng_container = DN(api.env.container_netgroup, api.env.basedn)
- for member in list(entry_attrs.get('memberofindirect', [])):
- memberdn = DN(member)
- if not memberdn.endswith(ng_container):
- continue
-
- filter = ldap.make_filter({'objectclass': 'mepmanagedentry'})
- try:
- ldap.get_entries(memberdn, ldap.SCOPE_BASE, filter, [''])
- except errors.NotFound:
- pass
- else:
- entry_attrs['memberofindirect'].remove(member)
-
-
-@register()
-class host_add(LDAPCreate):
- __doc__ = _('Add a new host.')
-
- has_output_params = LDAPCreate.has_output_params + host_output_params
- msg_summary = _('Added host "%(value)s"')
- member_attributes = ['managedby']
- takes_options = LDAPCreate.takes_options + (
- Flag('force',
- label=_('Force'),
- doc=_('force host name even if not in DNS'),
- ),
- Flag('no_reverse',
- doc=_('skip reverse DNS detection'),
- ),
- Str('ip_address?', validate_ipaddr,
- doc=_('Add the host to DNS with this IP address'),
- label=_('IP Address'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- if options.get('ip_address') and dns_container_exists(ldap):
- parts = keys[-1].split('.')
- host = parts[0]
- domain = unicode('.'.join(parts[1:]))
- check_reverse = not options.get('no_reverse', False)
- add_records_for_host_validation('ip_address',
- DNSName(host),
- DNSName(domain).make_absolute(),
- options['ip_address'],
- check_forward=True,
- check_reverse=check_reverse)
- if not options.get('force', False) and not 'ip_address' in options:
- util.verify_host_resolvable(keys[-1])
- if 'locality' in entry_attrs:
- entry_attrs['l'] = entry_attrs['locality']
- entry_attrs['cn'] = keys[-1]
- entry_attrs['serverhostname'] = keys[-1].split('.', 1)[0]
- if not entry_attrs.get('userpassword', False) and not options.get('random', False):
- entry_attrs['krbprincipalname'] = 'host/%s@%s' % (
- keys[-1], self.api.env.realm
- )
- if 'krbprincipalaux' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('krbprincipalaux')
- if 'krbprincipal' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('krbprincipal')
- else:
- if 'krbprincipalaux' in entry_attrs['objectclass']:
- entry_attrs['objectclass'].remove('krbprincipalaux')
- if 'krbprincipal' in entry_attrs['objectclass']:
- entry_attrs['objectclass'].remove('krbprincipal')
- if options.get('random'):
- entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars)
- # save the password so it can be displayed in post_callback
- setattr(context, 'randompassword', entry_attrs['userpassword'])
- certs = options.get('usercertificate', [])
- certs_der = [x509.normalize_certificate(c) for c in certs]
- for cert in certs_der:
- x509.verify_cert_subject(ldap, keys[-1], cert)
- entry_attrs['usercertificate'] = certs_der
- entry_attrs['managedby'] = dn
- entry_attrs['objectclass'].append('ieee802device')
- entry_attrs['objectclass'].append('ipasshhost')
- update_krbticketflags(ldap, entry_attrs, attrs_list, options, False)
- if 'krbticketflags' in entry_attrs:
- entry_attrs['objectclass'].append('krbticketpolicyaux')
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- exc = None
- if dns_container_exists(ldap):
- try:
- parts = keys[-1].split('.')
- host = parts[0]
- domain = unicode('.'.join(parts[1:]))
-
- if options.get('ip_address'):
- add_reverse = not options.get('no_reverse', False)
-
- add_records_for_host(DNSName(host),
- DNSName(domain).make_absolute(),
- options['ip_address'],
- add_forward=True,
- add_reverse=add_reverse)
- del options['ip_address']
-
- update_sshfp_record(domain, unicode(parts[0]), entry_attrs)
- except Exception as e:
- exc = e
- if options.get('random', False):
- try:
- entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
- except AttributeError:
- # On the off-chance some other extension deletes this from the
- # context, don't crash.
- pass
- if exc:
- raise errors.NonFatalError(
- reason=_('The host was added but the DNS update failed with: %(exc)s') % dict(exc=exc)
- )
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
-
- if options.get('all', False):
- entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- if entry_attrs['has_password']:
- # If an OTP is set there is no keytab, at least not one
- # fetched anywhere.
- entry_attrs['has_keytab'] = False
-
- convert_sshpubkey_post(entry_attrs)
-
- return dn
-
-
-@register()
-class host_del(LDAPDelete):
- __doc__ = _('Delete a host.')
-
- msg_summary = _('Deleted host "%(value)s"')
- member_attributes = ['managedby']
-
- takes_options = LDAPDelete.takes_options + (
- Flag('updatedns?',
- doc=_('Remove A, AAAA, SSHFP and PTR records of the host(s) '
- 'managed by IPA DNS'),
- default=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- # If we aren't given a fqdn, find it
- if _hostname_validator(None, keys[-1]) is not None:
- hostentry = api.Command['host_show'](keys[-1])['result']
- fqdn = hostentry['fqdn'][0]
- else:
- fqdn = keys[-1]
- host_is_master(ldap, fqdn)
- # Remove all service records for this host
- truncated = True
- while truncated:
- try:
- ret = api.Command['service_find'](fqdn)
- truncated = ret['truncated']
- services = ret['result']
- except errors.NotFound:
- break
- else:
- for entry_attrs in services:
- principal = entry_attrs['krbprincipalname'][0]
- (service, hostname, realm) = split_principal(principal)
- if hostname.lower() == fqdn:
- api.Command['service_del'](principal)
- updatedns = options.get('updatedns', False)
- if updatedns:
- try:
- updatedns = dns_container_exists(ldap)
- except errors.NotFound:
- updatedns = False
-
- if updatedns:
- # Remove A, AAAA, SSHFP and PTR records of the host
- parts = fqdn.split('.')
- domain = unicode('.'.join(parts[1:]))
- # Get all resources for this host
- rec_removed = False
- try:
- record = api.Command['dnsrecord_show'](
- domain, parts[0])['result']
- except errors.NotFound:
- pass
- else:
- # remove PTR records first
- for attr in ('arecord', 'aaaarecord'):
- for val in record.get(attr, []):
- rec_removed = (
- remove_ptr_rec(val, parts[0], domain) or
- rec_removed
- )
- try:
- # remove all A, AAAA, SSHFP records of the host
- api.Command['dnsrecord_mod'](
- domain,
- record['idnsname'][0],
- arecord=[],
- aaaarecord=[],
- sshfprecord=[]
- )
- except errors.EmptyModlist:
- pass
- else:
- rec_removed = True
-
- if not rec_removed:
- self.add_message(
- messages.FailedToRemoveHostDNSRecords(
- host=fqdn,
- reason=_("No A, AAAA, SSHFP or PTR records found.")
- )
- )
-
- if self.api.Command.ca_is_enabled()['result']:
- try:
- entry_attrs = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- revoke_certs(entry_attrs.get('usercertificate', []), self.log)
-
- return dn
-
-
-@register()
-class host_mod(LDAPUpdate):
- __doc__ = _('Modify information about a host.')
-
- has_output_params = LDAPUpdate.has_output_params + host_output_params
- msg_summary = _('Modified host "%(value)s"')
- member_attributes = ['managedby']
-
- takes_options = LDAPUpdate.takes_options + (
- Str('krbprincipalname?',
- cli_name='principalname',
- label=_('Principal name'),
- doc=_('Kerberos principal name for this host'),
- attribute=True,
- ),
- Flag('updatedns?',
- doc=_('Update DNS entries'),
- default=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # Allow an existing OTP to be reset but don't allow a OTP to be
- # added to an enrolled host.
- if options.get('userpassword') or options.get('random'):
- entry = {}
- self.obj.get_password_attributes(ldap, dn, entry)
- if not entry['has_password'] and entry['has_keytab']:
- raise errors.ValidationError(
- name='password',
- error=_('Password cannot be set on enrolled host.'))
-
- # Once a principal name is set it cannot be changed
- if 'cn' in entry_attrs:
- raise errors.ACIError(info=_('cn is immutable'))
- if 'locality' in entry_attrs:
- entry_attrs['l'] = entry_attrs['locality']
- if 'krbprincipalname' in entry_attrs:
- entry_attrs_old = ldap.get_entry(
- dn, ['objectclass', 'krbprincipalname']
- )
- if 'krbprincipalname' in entry_attrs_old:
- msg = 'Principal name already set, it is unchangeable.'
- raise errors.ACIError(info=msg)
- obj_classes = entry_attrs_old['objectclass']
- if 'krbprincipalaux' not in obj_classes:
- obj_classes.append('krbprincipalaux')
- entry_attrs['objectclass'] = obj_classes
-
- # verify certificates
- certs = entry_attrs.get('usercertificate') or []
- certs_der = [x509.normalize_certificate(c) for c in certs]
- for cert in certs_der:
- x509.verify_cert_subject(ldap, keys[-1], cert)
-
- # revoke removed certificates
- if certs and self.api.Command.ca_is_enabled()['result']:
- try:
- entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- old_certs = entry_attrs_old.get('usercertificate', [])
- old_certs_der = [x509.normalize_certificate(c) for c in old_certs]
- removed_certs_der = set(old_certs_der) - set(certs_der)
- revoke_certs(removed_certs_der, self.log)
-
- if certs:
- entry_attrs['usercertificate'] = certs_der
-
- if options.get('random'):
- entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars)
- setattr(context, 'randompassword', entry_attrs['userpassword'])
-
- if 'macaddress' in entry_attrs:
- if 'objectclass' in entry_attrs:
- obj_classes = entry_attrs['objectclass']
- else:
- _entry_attrs = ldap.get_entry(dn, ['objectclass'])
- obj_classes = _entry_attrs['objectclass']
- if 'ieee802device' not in obj_classes:
- obj_classes.append('ieee802device')
- entry_attrs['objectclass'] = obj_classes
-
- if options.get('updatedns', False) and dns_container_exists(ldap):
- parts = keys[-1].split('.')
- domain = unicode('.'.join(parts[1:]))
- try:
- result = api.Command['dnszone_show'](domain)['result']
- domain = result['idnsname'][0]
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- update_sshfp_record(domain, unicode(parts[0]), entry_attrs)
-
- if 'ipasshpubkey' in entry_attrs:
- if 'objectclass' in entry_attrs:
- obj_classes = entry_attrs['objectclass']
- else:
- _entry_attrs = ldap.get_entry(dn, ['objectclass'])
- obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass']
- if 'ipasshhost' not in obj_classes:
- obj_classes.append('ipasshhost')
-
- update_krbticketflags(ldap, entry_attrs, attrs_list, options, True)
-
- if 'krbticketflags' in entry_attrs:
- if 'objectclass' not in entry_attrs:
- entry_attrs_old = ldap.get_entry(dn, ['objectclass'])
- entry_attrs['objectclass'] = entry_attrs_old['objectclass']
- if 'krbticketpolicyaux' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('krbticketpolicyaux')
-
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- if options.get('random', False):
- entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- if entry_attrs['has_password']:
- # If an OTP is set there is no keytab, at least not one
- # fetched anywhere.
- entry_attrs['has_keytab'] = False
-
- if options.get('all', False):
- entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
-
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
-
- convert_sshpubkey_post(entry_attrs)
- remove_sshpubkey_from_output_post(self.context, entry_attrs)
- convert_ipaassignedidview_post(entry_attrs, options)
-
- return dn
-
-
-@register()
-class host_find(LDAPSearch):
- __doc__ = _('Search for hosts.')
-
- has_output_params = LDAPSearch.has_output_params + host_output_params
- msg_summary = ngettext(
- '%(count)d host matched', '%(count)d hosts matched', 0
- )
- member_attributes = ['memberof', 'enrolledby', 'managedby']
-
- def get_options(self):
- for option in super(host_find, self).get_options():
- yield option
- # "managing" membership has to be added and processed separately
- for option in self.get_member_options('managing'):
- yield option
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- if 'locality' in attrs_list:
- attrs_list.remove('locality')
- attrs_list.append('l')
- if 'man_host' in options or 'not_man_host' in options:
- hosts = []
- if options.get('man_host') is not None:
- for pkey in options.get('man_host', []):
- dn = self.obj.get_dn(pkey)
- try:
- entry_attrs = ldap.get_entry(dn, ['managedby'])
- except errors.NotFound:
- self.obj.handle_not_found(pkey)
- hosts.append(set(entry_attrs.get('managedby', '')))
- hosts = list(reduce(lambda s1, s2: s1 & s2, hosts))
-
- if not hosts:
- # There is no host managing _all_ hosts in --man-hosts
- filter = ldap.combine_filters(
- (filter, '(objectclass=disabled)'), ldap.MATCH_ALL
- )
-
- not_hosts = []
- if options.get('not_man_host') is not None:
- for pkey in options.get('not_man_host', []):
- dn = self.obj.get_dn(pkey)
- try:
- entry_attrs = ldap.get_entry(dn, ['managedby'])
- except errors.NotFound:
- self.obj.handle_not_found(pkey)
- not_hosts += entry_attrs.get('managedby', [])
- not_hosts = list(set(not_hosts))
-
- for target_hosts, filter_op in ((hosts, ldap.MATCH_ANY),
- (not_hosts, ldap.MATCH_NONE)):
- hosts_avas = [DN(host)[0][0] for host in target_hosts]
- hosts_filters = [ldap.make_filter_from_attr(ava.attr, ava.value)
- for ava in hosts_avas]
- hosts_filter = ldap.combine_filters(hosts_filters, filter_op)
-
- filter = ldap.combine_filters(
- (filter, hosts_filter), ldap.MATCH_ALL
- )
-
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
-
- return (filter.replace('locality', 'l'), base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- for entry_attrs in entries:
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
-
- if options.get('all', False):
- entry_attrs['managing'] = self.obj.get_managed_hosts(entry_attrs.dn)
-
- convert_sshpubkey_post(entry_attrs)
- remove_sshpubkey_from_output_post(self.context, entry_attrs)
- convert_ipaassignedidview_post(entry_attrs, options)
-
- remove_sshpubkey_from_output_list_post(self.context, entries)
-
- return truncated
-
-
-@register()
-class host_show(LDAPRetrieve):
- __doc__ = _('Display information about a host.')
-
- has_output_params = LDAPRetrieve.has_output_params + host_output_params
- takes_options = LDAPRetrieve.takes_options + (
- Str('out?',
- doc=_('file to store certificate in'),
- ),
- )
-
- member_attributes = ['managedby']
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- add_sshpubkey_to_attrs_pre(self.context, attrs_list)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- if entry_attrs['has_password']:
- # If an OTP is set there is no keytab, at least not one
- # fetched anywhere.
- entry_attrs['has_keytab'] = False
-
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
-
- if options.get('all', False):
- entry_attrs['managing'] = self.obj.get_managed_hosts(dn)
-
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
-
- convert_sshpubkey_post(entry_attrs)
- remove_sshpubkey_from_output_post(self.context, entry_attrs)
- convert_ipaassignedidview_post(entry_attrs, options)
-
- return dn
-
-
-@register()
-class host_disable(LDAPQuery):
- __doc__ = _('Disable the Kerberos key, SSL certificate and all services of a host.')
-
- has_output = output.standard_value
- msg_summary = _('Disabled host "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- # If we aren't given a fqdn, find it
- if _hostname_validator(None, keys[-1]) is not None:
- hostentry = api.Command['host_show'](keys[-1])['result']
- fqdn = hostentry['fqdn'][0]
- else:
- fqdn = keys[-1]
-
- host_is_master(ldap, fqdn)
-
- # See if we actually do anthing here, and if not raise an exception
- done_work = False
-
- truncated = True
- while truncated:
- try:
- ret = api.Command['service_find'](fqdn)
- truncated = ret['truncated']
- services = ret['result']
- except errors.NotFound:
- break
- else:
- for entry_attrs in services:
- principal = entry_attrs['krbprincipalname'][0]
- (service, hostname, realm) = split_principal(principal)
- if hostname.lower() == fqdn:
- try:
- api.Command['service_disable'](principal)
- done_work = True
- except errors.AlreadyInactive:
- pass
-
- dn = self.obj.get_dn(*keys, **options)
- try:
- entry_attrs = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if self.api.Command.ca_is_enabled()['result']:
- certs = entry_attrs.get('usercertificate', [])
-
- if certs:
- revoke_certs(certs, self.log)
- # Remove the usercertificate altogether
- entry_attrs['usercertificate'] = None
- ldap.update_entry(entry_attrs)
- done_work = True
-
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- if entry_attrs['has_keytab']:
- ldap.remove_principal_key(dn)
- done_work = True
-
- if not done_work:
- raise errors.AlreadyInactive()
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
- return dn
-
-
-@register()
-class host_add_managedby(LDAPAddMember):
- __doc__ = _('Add hosts that can manage this host.')
-
- member_attributes = ['managedby']
- has_output_params = LDAPAddMember.has_output_params + host_output_params
- allow_same = True
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
- return (completed, dn)
-
-
-@register()
-class host_remove_managedby(LDAPRemoveMember):
- __doc__ = _('Remove hosts that can manage this host.')
-
- member_attributes = ['managedby']
- has_output_params = LDAPRemoveMember.has_output_params + host_output_params
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, entry_attrs)
- return (completed, dn)
-
-
-@register()
-class host_allow_retrieve_keytab(LDAPAddMember):
- __doc__ = _('Allow users, groups, hosts or host groups to retrieve a keytab'
- ' of this host.')
- member_attributes = ['ipaallowedtoperform_read_keys']
- has_output_params = LDAPAddMember.has_output_params + host_output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- add_missing_object_class(ldap, u'ipaallowedoperations', dn)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class host_disallow_retrieve_keytab(LDAPRemoveMember):
- __doc__ = _('Disallow users, groups, hosts or host groups to retrieve a '
- 'keytab of this host.')
- member_attributes = ['ipaallowedtoperform_read_keys']
- has_output_params = LDAPRemoveMember.has_output_params + host_output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class host_allow_create_keytab(LDAPAddMember):
- __doc__ = _('Allow users, groups, hosts or host groups to create a keytab '
- 'of this host.')
- member_attributes = ['ipaallowedtoperform_write_keys']
- has_output_params = LDAPAddMember.has_output_params + host_output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- add_missing_object_class(ldap, u'ipaallowedoperations', dn)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class host_disallow_create_keytab(LDAPRemoveMember):
- __doc__ = _('Disallow users, groups, hosts or host groups to create a '
- 'keytab of this host.')
- member_attributes = ['ipaallowedtoperform_write_keys']
- has_output_params = LDAPRemoveMember.has_output_params + host_output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class host_add_cert(LDAPAddAttribute):
- __doc__ = _('Add certificates to host entry')
- msg_summary = _('Added certificates to host "%(value)s"')
- attribute = 'usercertificate'
-
-
-@register()
-class host_remove_cert(LDAPRemoveAttribute):
- __doc__ = _('Remove certificates from host entry')
- msg_summary = _('Removed certificates from host "%(value)s"')
- attribute = 'usercertificate'
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- if 'usercertificate' in options:
- revoke_certs(options['usercertificate'], self.log)
-
- return dn
diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py
deleted file mode 100644
index dab354d9c..000000000
--- a/ipalib/plugins/hostgroup.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# 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 ipalib.plugable import Registry
-from .baseldap import (LDAPObject, LDAPCreate, LDAPRetrieve,
- LDAPDelete, LDAPUpdate, LDAPSearch,
- LDAPAddMember, LDAPRemoveMember,
- entry_from_entry, wait_for_value)
-from ipalib import Str, api, _, ngettext, errors
-from .netgroup import NETGROUP_PATTERN, NETGROUP_PATTERN_ERRMSG
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Groups of hosts.
-
-Manage groups of hosts. This is useful for applying access control to a
-number of hosts by using Host-based Access Control.
-
-EXAMPLES:
-
- Add a new host group:
- ipa hostgroup-add --desc="Baltimore hosts" baltimore
-
- Add another new host group:
- ipa hostgroup-add --desc="Maryland hosts" maryland
-
- Add members to the hostgroup (using Bash brace expansion):
- ipa hostgroup-add-member --hosts={box1,box2,box3} baltimore
-
- Add a hostgroup as a member of another hostgroup:
- ipa hostgroup-add-member --hostgroups=baltimore maryland
-
- Remove a host from the hostgroup:
- ipa hostgroup-remove-member --hosts=box2 baltimore
-
- Display a host group:
- ipa hostgroup-show baltimore
-
- Delete a hostgroup:
- ipa hostgroup-del baltimore
-""")
-
-
-def get_complete_hostgroup_member_list(hostgroup):
- result = api.Command['hostgroup_show'](hostgroup)['result']
- direct = list(result.get('member_host', []))
- indirect = list(result.get('memberindirect_host', []))
- return direct + indirect
-
-
-register = Registry()
-
-PROTECTED_HOSTGROUPS = (u'ipaservers',)
-
-
-@register()
-class hostgroup(LDAPObject):
- """
- Hostgroup object.
- """
- container_dn = api.env.container_hostgroup
- object_name = _('host group')
- object_name_plural = _('host groups')
- object_class = ['ipaobject', 'ipahostgroup']
- permission_filter_objectclasses = ['ipahostgroup']
- search_attributes = ['cn', 'description', 'member', 'memberof']
- default_attributes = ['cn', 'description', 'member', 'memberof',
- 'memberindirect', 'memberofindirect',
- ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'member': ['host', 'hostgroup'],
- 'memberof': ['hostgroup', 'netgroup', 'hbacrule', 'sudorule'],
- 'memberindirect': ['host', 'hostgroup'],
- 'memberofindirect': ['hostgroup', 'hbacrule', 'sudorule'],
- }
- managed_permissions = {
- 'System: Read Hostgroups': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'ipauniqueid', 'o',
- 'objectclass', 'ou', 'owner', 'seealso',
- },
- },
- 'System: Read Hostgroup Membership': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'member', 'memberof', 'memberuser', 'memberhost',
- },
- },
- 'System: Add Hostgroups': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Hostgroups";allow (add) groupdn = "ldap:///cn=Add Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Group Administrators'},
- },
- 'System: Modify Hostgroup Membership': {
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': [
- '(objectclass=ipahostgroup)',
- '(!(cn=ipaservers))',
- ],
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Hostgroup membership";allow (write) groupdn = "ldap:///cn=Modify Hostgroup membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Group Administrators'},
- },
- 'System: Modify Hostgroups': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'cn', 'description'},
- 'replaces': [
- '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0; acl "permission:Modify Hostgroups";allow (write) groupdn = "ldap:///cn=Modify Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Group Administrators'},
- },
- 'System: Remove Hostgroups': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Hostgroups";allow (delete) groupdn = "ldap:///cn=Remove Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Host Group Administrators'},
- },
- }
-
- label = _('Host Groups')
- label_singular = _('Host Group')
-
- takes_params = (
- Str('cn',
- pattern=NETGROUP_PATTERN,
- pattern_errmsg=NETGROUP_PATTERN_ERRMSG,
- cli_name='hostgroup_name',
- label=_('Host-group'),
- doc=_('Name of host-group'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this host-group'),
- ),
- )
-
- def suppress_netgroup_memberof(self, ldap, dn, entry_attrs):
- """
- We don't want to show managed netgroups so remove them from the
- memberOf list.
- """
- hgdn = DN(dn)
- for member in list(entry_attrs.get('memberof', [])):
- ngdn = DN(member)
- if ngdn['cn'] != hgdn['cn']:
- continue
-
- filter = ldap.make_filter({'objectclass': 'mepmanagedentry'})
- try:
- ldap.get_entries(ngdn, ldap.SCOPE_BASE, filter, [''])
- except errors.NotFound:
- pass
- else:
- entry_attrs['memberof'].remove(member)
-
-
-@register()
-class hostgroup_add(LDAPCreate):
- __doc__ = _('Add a new hostgroup.')
-
- msg_summary = _('Added hostgroup "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- # check duplicity with hostgroups first to provide proper error
- api.Object['hostgroup'].get_dn_if_exists(keys[-1])
- self.obj.handle_duplicate_entry(*keys)
- except errors.NotFound:
- pass
-
- try:
- # when enabled, a managed netgroup is created for every hostgroup
- # make sure that the netgroup can be created
- api.Object['netgroup'].get_dn_if_exists(keys[-1])
- raise errors.DuplicateEntry(message=unicode(_(
- u'netgroup with name "%s" already exists. '
- u'Hostgroups and netgroups share a common namespace'
- ) % keys[-1]))
- except errors.NotFound:
- pass
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- # Always wait for the associated netgroup to be created so we can
- # be sure to ignore it in memberOf
- newentry = wait_for_value(ldap, dn, 'objectclass', 'mepOriginEntry')
- entry_from_entry(entry_attrs, newentry)
- self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
-
- return dn
-
-
-@register()
-class hostgroup_del(LDAPDelete):
- __doc__ = _('Delete a hostgroup.')
-
- msg_summary = _('Deleted hostgroup "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- if keys[0] in PROTECTED_HOSTGROUPS:
- raise errors.ProtectedEntryError(label=_(u'hostgroup'),
- key=keys[0],
- reason=_(u'privileged hostgroup'))
-
- return dn
-
-
-@register()
-class hostgroup_mod(LDAPUpdate):
- __doc__ = _('Modify a hostgroup.')
-
- msg_summary = _('Modified hostgroup "%(value)s"')
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
- return dn
-
-
-@register()
-class hostgroup_find(LDAPSearch):
- __doc__ = _('Search for hostgroups.')
-
- member_attributes = ['member', 'memberof']
- msg_summary = ngettext(
- '%(count)d hostgroup matched', '%(count)d hostgroups matched', 0
- )
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- for entry in entries:
- self.obj.suppress_netgroup_memberof(ldap, entry.dn, entry)
- return truncated
-
-
-@register()
-class hostgroup_show(LDAPRetrieve):
- __doc__ = _('Display information about a hostgroup.')
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
- return dn
-
-
-@register()
-class hostgroup_add_member(LDAPAddMember):
- __doc__ = _('Add members to a hostgroup.')
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
- return (completed, dn)
-
-
-@register()
-class hostgroup_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from a hostgroup.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- if keys[0] in PROTECTED_HOSTGROUPS and 'host' in options:
- result = api.Command.hostgroup_show(keys[0])
- hosts_left = set(result['result'].get('member_host', []))
- hosts_deleted = set(options['host'])
- if hosts_left.issubset(hosts_deleted):
- raise errors.LastMemberError(key=sorted(hosts_deleted)[0],
- label=_(u'hostgroup'),
- container=keys[0])
-
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.suppress_netgroup_memberof(ldap, dn, entry_attrs)
- return (completed, dn)
-
diff --git a/ipalib/plugins/idrange.py b/ipalib/plugins/idrange.py
deleted file mode 100644
index ccd67995e..000000000
--- a/ipalib/plugins/idrange.py
+++ /dev/null
@@ -1,769 +0,0 @@
-# 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/>.
-
-import six
-
-from ipalib.plugable import Registry
-from .baseldap import (LDAPObject, LDAPCreate, LDAPDelete,
- LDAPRetrieve, LDAPSearch, LDAPUpdate)
-from ipalib import api, Int, Str, StrEnum, _, ngettext
-from ipalib import errors
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- import ipaserver.dcerpc
- _dcerpc_bindings_installed = True
- except ImportError:
- _dcerpc_bindings_installed = False
-
-ID_RANGE_VS_DNA_WARNING = """=======
-WARNING:
-
-DNA plugin in 389-ds will allocate IDs based on the ranges configured for the
-local domain. Currently the DNA plugin *cannot* be reconfigured itself based
-on the local ranges set via this family of commands.
-
-Manual configuration change has to be done in the DNA plugin configuration for
-the new local range. Specifically, The dnaNextRange attribute of 'cn=Posix
-IDs,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config' has to be
-modified to match the new range.
-=======
-"""
-
-__doc__ = _("""
-ID ranges
-
-Manage ID ranges used to map Posix IDs to SIDs and back.
-
-There are two type of ID ranges which are both handled by this utility:
-
- - the ID ranges of the local domain
- - the ID ranges of trusted remote domains
-
-Both types have the following attributes in common:
-
- - base-id: the first ID of the Posix ID range
- - range-size: the size of the range
-
-With those two attributes a range object can reserve the Posix IDs starting
-with base-id up to but not including base-id+range-size exclusively.
-
-Additionally an ID range of the local domain may set
- - rid-base: the first RID(*) of the corresponding RID range
- - secondary-rid-base: first RID of the secondary RID range
-
-and an ID range of a trusted domain must set
- - rid-base: the first RID of the corresponding RID range
- - sid: domain SID of the trusted domain
-
-
-
-EXAMPLE: Add a new ID range for a trusted domain
-
-Since there might be more than one trusted domain the domain SID must be given
-while creating the ID range.
-
- ipa idrange-add --base-id=1200000 --range-size=200000 --rid-base=0 \\
- --dom-sid=S-1-5-21-123-456-789 trusted_dom_range
-
-This ID range is then used by the IPA server and the SSSD IPA provider to
-assign Posix UIDs to users from the trusted domain.
-
-If e.g a range for a trusted domain is configured with the following values:
- base-id = 1200000
- range-size = 200000
- rid-base = 0
-the RIDs 0 to 199999 are mapped to the Posix ID from 1200000 to 13999999. So
-RID 1000 <-> Posix ID 1201000
-
-
-
-EXAMPLE: Add a new ID range for the local domain
-
-To create an ID range for the local domain it is not necessary to specify a
-domain SID. But since it is possible that a user and a group can have the same
-value as Posix ID a second RID interval is needed to handle conflicts.
-
- ipa idrange-add --base-id=1200000 --range-size=200000 --rid-base=1000 \\
- --secondary-rid-base=1000000 local_range
-
-The data from the ID ranges of the local domain are used by the IPA server
-internally to assign SIDs to IPA users and groups. The SID will then be stored
-in the user or group objects.
-
-If e.g. the ID range for the local domain is configured with the values from
-the example above then a new user with the UID 1200007 will get the RID 1007.
-If this RID is already used by a group the RID will be 1000007. This can only
-happen if a user or a group object was created with a fixed ID because the
-automatic assignment will not assign the same ID twice. Since there are only
-users and groups sharing the same ID namespace it is sufficient to have only
-one fallback range to handle conflicts.
-
-To find the Posix ID for a given RID from the local domain it has to be
-checked first if the RID falls in the primary or secondary RID range and
-the rid-base or the secondary-rid-base has to be subtracted, respectively,
-and the base-id has to be added to get the Posix ID.
-
-Typically the creation of ID ranges happens behind the scenes and this CLI
-must not be used at all. The ID range for the local domain will be created
-during installation or upgrade from an older version. The ID range for a
-trusted domain will be created together with the trust by 'ipa trust-add ...'.
-
-USE CASES:
-
- Add an ID range from a transitively trusted domain
-
- If the trusted domain (A) trusts another domain (B) as well and this trust
- is transitive 'ipa trust-add domain-A' will only create a range for
- domain A. The ID range for domain B must be added manually.
-
- Add an additional ID range for the local domain
-
- If the ID range of the local domain is exhausted, i.e. no new IDs can be
- assigned to Posix users or groups by the DNA plugin, a new range has to be
- created to allow new users and groups to be added. (Currently there is no
- connection between this range CLI and the DNA plugin, but a future version
- might be able to modify the configuration of the DNS plugin as well)
-
-In general it is not necessary to modify or delete ID ranges. If there is no
-other way to achieve a certain configuration than to modify or delete an ID
-range it should be done with great care. Because UIDs are stored in the file
-system and are used for access control it might be possible that users are
-allowed to access files of other users if an ID range got deleted and reused
-for a different domain.
-
-(*) The RID is typically the last integer of a user or group SID which follows
-the domain SID. E.g. if the domain SID is S-1-5-21-123-456-789 and a user from
-this domain has the SID S-1-5-21-123-456-789-1010 then 1010 id the RID of the
-user. RIDs are unique in a domain, 32bit values and are used for users and
-groups.
-
-{0}
-""".format(ID_RANGE_VS_DNA_WARNING))
-
-register = Registry()
-
-@register()
-class idrange(LDAPObject):
- """
- Range object.
- """
-
- range_type = ('domain', 'ad', 'ipa')
- container_dn = api.env.container_ranges
- object_name = ('range')
- object_name_plural = ('ranges')
- object_class = ['ipaIDrange']
- permission_filter_objectclasses = ['ipaidrange']
- possible_objectclasses = ['ipadomainidrange', 'ipatrustedaddomainrange']
- default_attributes = ['cn', 'ipabaseid', 'ipaidrangesize', 'ipabaserid',
- 'ipasecondarybaserid', 'ipanttrusteddomainsid',
- 'iparangetype']
- managed_permissions = {
- 'System: Read ID Ranges': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass',
- 'ipabaseid', 'ipaidrangesize', 'iparangetype',
- 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid',
- },
- },
- }
-
- label = _('ID Ranges')
- label_singular = _('ID Range')
-
- # The commented range types are planned but not yet supported
- range_types = {
- u'ipa-local': unicode(_('local domain range')),
- # u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
- u'ipa-ad-trust': unicode(_('Active Directory domain range')),
- u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
- 'POSIX attributes')),
- # u'ipa-ipa-trust': unicode(_('IPA trust range')),
- }
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Range name'),
- primary_key=True,
- ),
- Int('ipabaseid',
- cli_name='base_id',
- label=_("First Posix ID of the range"),
- ),
- Int('ipaidrangesize',
- cli_name='range_size',
- label=_("Number of IDs in the range"),
- ),
- Int('ipabaserid?',
- cli_name='rid_base',
- label=_('First RID of the corresponding RID range'),
- ),
- Int('ipasecondarybaserid?',
- cli_name='secondary_rid_base',
- label=_('First RID of the secondary RID range'),
- ),
- Str('ipanttrusteddomainsid?',
- cli_name='dom_sid',
- flags=('no_update',),
- label=_('Domain SID of the trusted domain'),
- ),
- Str('ipanttrusteddomainname?',
- cli_name='dom_name',
- flags=('no_search', 'virtual_attribute', 'no_update'),
- label=_('Name of the trusted domain'),
- ),
- StrEnum('iparangetype?',
- label=_('Range type'),
- cli_name='type',
- doc=(_('ID range type, one of {vals}'
- .format(vals=', '.join(range_types.keys())))),
- values=tuple(range_types.keys()),
- flags=['no_update'],
- )
- )
-
- def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False):
- if not any((options.get('pkey_only', False),
- options.get('raw', False))):
- range_type = entry_attrs['iparangetype'][0]
- entry_attrs['iparangetyperaw'] = [range_type]
- entry_attrs['iparangetype'] = [self.range_types.get(range_type, None)]
-
- # Remove the objectclass
- if not keep_objectclass:
- if not options.get('all', False) or options.get('pkey_only', False):
- entry_attrs.pop('objectclass', None)
-
- def handle_ipabaserid(self, entry_attrs, options):
- if any((options.get('pkey_only', False), options.get('raw', False))):
- return
- if entry_attrs['iparangetype'][0] == u'ipa-ad-trust-posix':
- entry_attrs.pop('ipabaserid', None)
-
- def check_ids_in_modified_range(self, old_base, old_size, new_base,
- new_size):
- if new_base is None and new_size is None:
- # nothing to check
- return
- if new_base is None:
- new_base = old_base
- if new_size is None:
- new_size = old_size
- old_interval = (old_base, old_base + old_size - 1)
- new_interval = (new_base, new_base + new_size - 1)
- checked_intervals = []
- low_diff = new_interval[0] - old_interval[0]
- if low_diff > 0:
- checked_intervals.append((old_interval[0],
- min(old_interval[1], new_interval[0] - 1)))
- high_diff = old_interval[1] - new_interval[1]
- if high_diff > 0:
- checked_intervals.append((max(old_interval[0], new_interval[1] + 1),
- old_interval[1]))
-
- if not checked_intervals:
- # range is equal or covers the entire old range, nothing to check
- return
-
- ldap = self.backend
- id_filter_base = ["(objectclass=posixAccount)",
- "(objectclass=posixGroup)",
- "(objectclass=ipaIDObject)"]
- id_filter_ids = []
-
- for id_low, id_high in checked_intervals:
- id_filter_ids.append("(&(uidNumber>=%(low)d)(uidNumber<=%(high)d))"
- % dict(low=id_low, high=id_high))
- id_filter_ids.append("(&(gidNumber>=%(low)d)(gidNumber<=%(high)d))"
- % dict(low=id_low, high=id_high))
- id_filter = ldap.combine_filters(
- [ldap.combine_filters(id_filter_base, "|"),
- ldap.combine_filters(id_filter_ids, "|")],
- "&")
-
- try:
- (objects, truncated) = ldap.find_entries(filter=id_filter,
- attrs_list=['uid', 'cn'],
- base_dn=DN(api.env.container_accounts, api.env.basedn))
- except errors.NotFound:
- # no objects in this range found, allow the command
- pass
- else:
- raise errors.ValidationError(name="ipabaseid,ipaidrangesize",
- error=_('range modification leaving objects with ID out '
- 'of the defined range is not allowed'))
-
- def get_domain_validator(self):
- if not _dcerpc_bindings_installed:
- raise errors.NotFound(reason=_('Cannot perform SID validation '
- 'without Samba 4 support installed. Make sure you have '
- 'installed server-trust-ad sub-package of IPA on the server'))
-
- domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
-
- if not domain_validator.is_configured():
- raise errors.NotFound(reason=_('Cross-realm trusts are not '
- 'configured. Make sure you have run ipa-adtrust-install '
- 'on the IPA server first'))
-
- return domain_validator
-
- def validate_trusted_domain_sid(self, sid):
-
- domain_validator = self.get_domain_validator()
-
- if not domain_validator.is_trusted_domain_sid_valid(sid):
- raise errors.ValidationError(name='domain SID',
- error=_('SID is not recognized as a valid SID for a '
- 'trusted domain'))
-
- def get_trusted_domain_sid_from_name(self, name):
- """ Returns unicode string representation for given trusted domain name
- or None if SID forthe given trusted domain name could not be found."""
-
- domain_validator = self.get_domain_validator()
-
- sid = domain_validator.get_sid_from_domain_name(name)
-
- if sid is not None:
- sid = unicode(sid)
-
- return sid
-
- # checks that primary and secondary rid ranges do not overlap
- def are_rid_ranges_overlapping(self, rid_base, secondary_rid_base, size):
-
- # if any of these is None, the check does not apply
- if any(attr is None for attr in (rid_base, secondary_rid_base, size)):
- return False
-
- # sort the bases
- if rid_base > secondary_rid_base:
- rid_base, secondary_rid_base = secondary_rid_base, rid_base
-
- # rid_base is now <= secondary_rid_base,
- # so the following check is sufficient
- if rid_base + size <= secondary_rid_base:
- return False
- else:
- return True
-
-
-@register()
-class idrange_add(LDAPCreate):
- __doc__ = _("""
- Add new ID range.
-
- To add a new ID range you always have to specify
-
- --base-id
- --range-size
-
- Additionally
-
- --rid-base
- --secondary-rid-base
-
- may be given for a new ID range for the local domain while
-
- --rid-base
- --dom-sid
-
- must be given to add a new range for a trusted AD domain.
-
-{0}
-""".format(ID_RANGE_VS_DNA_WARNING))
-
- msg_summary = _('Added ID range "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- is_set = lambda x: (x in entry_attrs) and (entry_attrs[x] is not None)
-
- # This needs to stay in options since there is no
- # ipanttrusteddomainname attribute in LDAP
- if 'ipanttrusteddomainname' in options:
- if is_set('ipanttrusteddomainsid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options dom-sid and dom-name '
- 'cannot be used together'))
-
- sid = self.obj.get_trusted_domain_sid_from_name(
- options['ipanttrusteddomainname'])
-
- if sid is not None:
- entry_attrs['ipanttrusteddomainsid'] = sid
- else:
- raise errors.ValidationError(name='ID Range setup',
- error=_('SID for the specified trusted domain name could '
- 'not be found. Please specify the SID directly '
- 'using dom-sid option.'))
-
- # ipaNTTrustedDomainSID attribute set, this is AD Trusted domain range
- if is_set('ipanttrusteddomainsid'):
- entry_attrs['objectclass'].append('ipatrustedaddomainrange')
-
- # Default to ipa-ad-trust if no type set
- if not is_set('iparangetype'):
- entry_attrs['iparangetype'] = u'ipa-ad-trust'
-
- if entry_attrs['iparangetype'] == u'ipa-ad-trust':
- if not is_set('ipabaserid'):
- raise errors.ValidationError(
- name='ID Range setup',
- error=_('Options dom-sid/dom-name and rid-base must '
- 'be used together')
- )
- elif entry_attrs['iparangetype'] == u'ipa-ad-trust-posix':
- if is_set('ipabaserid') and entry_attrs['ipabaserid'] != 0:
- raise errors.ValidationError(
- name='ID Range setup',
- error=_('Option rid-base must not be used when IPA '
- 'range type is ipa-ad-trust-posix')
- )
- else:
- entry_attrs['ipabaserid'] = 0
- else:
- raise errors.ValidationError(name='ID Range setup',
- error=_('IPA Range type must be one of ipa-ad-trust '
- 'or ipa-ad-trust-posix when SID of the trusted '
- 'domain is specified'))
-
- if is_set('ipasecondarybaserid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options dom-sid/dom-name and secondary-rid-base '
- 'cannot be used together'))
-
- # Validate SID as the one of trusted domains
- self.obj.validate_trusted_domain_sid(
- entry_attrs['ipanttrusteddomainsid'])
-
- # ipaNTTrustedDomainSID attribute not set, this is local domain range
- else:
- entry_attrs['objectclass'].append('ipadomainidrange')
-
- # Default to ipa-local if no type set
- if 'iparangetype' not in entry_attrs:
- entry_attrs['iparangetype'] = 'ipa-local'
-
- # TODO: can also be ipa-ad-winsync here?
- if entry_attrs['iparangetype'] in (u'ipa-ad-trust',
- u'ipa-ad-trust-posix'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('IPA Range type must not be one of ipa-ad-trust '
- 'or ipa-ad-trust-posix when SID of the trusted '
- 'domain is not specified.'))
-
- # secondary base rid must be set if and only if base rid is set
- if is_set('ipasecondarybaserid') != is_set('ipabaserid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options secondary-rid-base and rid-base must '
- 'be used together'))
-
- # and they must not overlap
- if is_set('ipabaserid') and is_set('ipasecondarybaserid'):
- if self.obj.are_rid_ranges_overlapping(
- entry_attrs['ipabaserid'],
- entry_attrs['ipasecondarybaserid'],
- entry_attrs['ipaidrangesize']):
- raise errors.ValidationError(name='ID Range setup',
- error=_("Primary RID range and secondary RID range"
- " cannot overlap"))
-
- # rid-base and secondary-rid-base must be set if
- # ipa-adtrust-install has been run on the system
- adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
-
- if adtrust_is_enabled and not (
- is_set('ipabaserid') and is_set('ipasecondarybaserid')):
- raise errors.ValidationError(
- name='ID Range setup',
- error=_(
- 'You must specify both rid-base and '
- 'secondary-rid-base options, because '
- 'ipa-adtrust-install has already been run.'
- )
- )
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.handle_ipabaserid(entry_attrs, options)
- self.obj.handle_iparangetype(entry_attrs, options,
- keep_objectclass=True)
- return dn
-
-
-@register()
-class idrange_del(LDAPDelete):
- __doc__ = _('Delete an ID range.')
-
- msg_summary = _('Deleted ID range "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- try:
- old_attrs = ldap.get_entry(dn, ['ipabaseid',
- 'ipaidrangesize',
- 'ipanttrusteddomainsid'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- # Check whether we leave any object with id in deleted range
- old_base_id = int(old_attrs.get('ipabaseid', [0])[0])
- old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0])
- self.obj.check_ids_in_modified_range(
- old_base_id, old_range_size, 0, 0)
-
- # Check whether the range does not belong to the active trust
- range_sid = old_attrs.get('ipanttrusteddomainsid')
-
- if range_sid is not None:
- # Search for trusted domain with SID specified in the ID range entry
- range_sid = range_sid[0]
- domain_filter=('(&(objectclass=ipaNTTrustedDomain)'
- '(ipanttrusteddomainsid=%s))' % range_sid)
-
- try:
- (trust_domains, truncated) = ldap.find_entries(
- base_dn=DN(api.env.container_trusts, api.env.basedn),
- filter=domain_filter)
- except errors.NotFound:
- pass
- else:
- # If there's an entry, it means that there's active domain
- # of a trust that this range belongs to, so raise a
- # DependentEntry error
- raise errors.DependentEntry(
- label='Active Trust domain',
- key=keys[0],
- dependent=trust_domains[0].dn[0].value)
-
-
- return dn
-
-
-@register()
-class idrange_find(LDAPSearch):
- __doc__ = _('Search for ranges.')
-
- msg_summary = ngettext(
- '%(count)d range matched', '%(count)d ranges matched', 0
- )
-
- # Since all range types are stored within separate containers under
- # 'cn=ranges,cn=etc' search can be done on a one-level scope
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args,
- **options):
- assert isinstance(base_dn, DN)
- attrs_list.append('objectclass')
- return (filters, base_dn, ldap.SCOPE_ONELEVEL)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for entry in entries:
- self.obj.handle_ipabaserid(entry, options)
- self.obj.handle_iparangetype(entry, options)
- return truncated
-
-
-@register()
-class idrange_show(LDAPRetrieve):
- __doc__ = _('Display information about a range.')
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- attrs_list.append('objectclass')
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.handle_ipabaserid(entry_attrs, options)
- self.obj.handle_iparangetype(entry_attrs, options)
- return dn
-
-
-@register()
-class idrange_mod(LDAPUpdate):
- __doc__ = _("""Modify ID range.
-
-{0}
-""".format(ID_RANGE_VS_DNA_WARNING))
-
- msg_summary = _('Modified ID range "%(value)s"')
-
- takes_options = LDAPUpdate.takes_options + (
- Str(
- 'ipanttrusteddomainsid?',
- deprecated=True,
- cli_name='dom_sid',
- flags=('no_update', 'no_option'),
- label=_('Domain SID of the trusted domain'),
- autofill=False,
- ),
- Str(
- 'ipanttrusteddomainname?',
- deprecated=True,
- cli_name='dom_name',
- flags=('no_search', 'virtual_attribute', 'no_update', 'no_option'),
- label=_('Name of the trusted domain'),
- autofill=False,
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- attrs_list.append('objectclass')
-
- try:
- old_attrs = ldap.get_entry(dn, ['*'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if old_attrs['iparangetype'][0] == 'ipa-local':
- raise errors.ExecutionError(
- message=_('This command can not be used to change ID '
- 'allocation for local IPA domain. Run '
- '`ipa help idrange` for more information')
- )
-
- is_set = lambda x: (x in entry_attrs) and (entry_attrs[x] is not None)
- in_updated_attrs = lambda x:\
- (x in entry_attrs and entry_attrs[x] is not None) or\
- (x not in entry_attrs and x in old_attrs
- and old_attrs[x] is not None)
-
- # This needs to stay in options since there is no
- # ipanttrusteddomainname attribute in LDAP
- if 'ipanttrusteddomainname' in options:
- if is_set('ipanttrusteddomainsid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options dom-sid and dom-name '
- 'cannot be used together'))
-
- sid = self.obj.get_trusted_domain_sid_from_name(
- options['ipanttrusteddomainname'])
-
- # we translate the name into sid so further validation can rely
- # on ipanttrusteddomainsid attribute only
- if sid is not None:
- entry_attrs['ipanttrusteddomainsid'] = sid
- else:
- raise errors.ValidationError(name='ID Range setup',
- error=_('SID for the specified trusted domain name could '
- 'not be found. Please specify the SID directly '
- 'using dom-sid option.'))
-
- if in_updated_attrs('ipanttrusteddomainsid'):
- if in_updated_attrs('ipasecondarybaserid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options dom-sid and secondary-rid-base cannot '
- 'be used together'))
- range_type = old_attrs['iparangetype'][0]
- if range_type == u'ipa-ad-trust':
- if not in_updated_attrs('ipabaserid'):
- raise errors.ValidationError(
- name='ID Range setup',
- error=_('Options dom-sid and rid-base must '
- 'be used together'))
- elif (range_type == u'ipa-ad-trust-posix' and
- 'ipabaserid' in entry_attrs):
- if entry_attrs['ipabaserid'] is None:
- entry_attrs['ipabaserid'] = 0
- elif entry_attrs['ipabaserid'] != 0:
- raise errors.ValidationError(
- name='ID Range setup',
- error=_('Option rid-base must not be used when IPA '
- 'range type is ipa-ad-trust-posix')
- )
-
- if is_set('ipanttrusteddomainsid'):
- # Validate SID as the one of trusted domains
- # perform this check only if the attribute was changed
- self.obj.validate_trusted_domain_sid(
- entry_attrs['ipanttrusteddomainsid'])
-
- # Add trusted AD domain range object class, if it wasn't there
- if not 'ipatrustedaddomainrange' in old_attrs['objectclass']:
- entry_attrs['objectclass'].append('ipatrustedaddomainrange')
-
- else:
- # secondary base rid must be set if and only if base rid is set
- if in_updated_attrs('ipasecondarybaserid') !=\
- in_updated_attrs('ipabaserid'):
- raise errors.ValidationError(name='ID Range setup',
- error=_('Options secondary-rid-base and rid-base must '
- 'be used together'))
-
- # ensure that primary and secondary rid ranges do not overlap
- if all(in_updated_attrs(base)
- for base in ('ipabaserid', 'ipasecondarybaserid')):
-
- # make sure we are working with updated attributes
- rid_range_attributes = ('ipabaserid', 'ipasecondarybaserid',
- 'ipaidrangesize')
- updated_values = dict()
-
- for attr in rid_range_attributes:
- if is_set(attr):
- updated_values[attr] = entry_attrs[attr]
- else:
- updated_values[attr] = int(old_attrs[attr][0])
-
- if self.obj.are_rid_ranges_overlapping(
- updated_values['ipabaserid'],
- updated_values['ipasecondarybaserid'],
- updated_values['ipaidrangesize']):
- raise errors.ValidationError(name='ID Range setup',
- error=_("Primary RID range and secondary RID range"
- " cannot overlap"))
-
- # check whether ids are in modified range
- old_base_id = int(old_attrs.get('ipabaseid', [0])[0])
- old_range_size = int(old_attrs.get('ipaidrangesize', [0])[0])
- new_base_id = entry_attrs.get('ipabaseid')
-
- if new_base_id is not None:
- new_base_id = int(new_base_id)
-
- new_range_size = entry_attrs.get('ipaidrangesize')
-
- if new_range_size is not None:
- new_range_size = int(new_range_size)
-
- self.obj.check_ids_in_modified_range(old_base_id, old_range_size,
- new_base_id, new_range_size)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.handle_ipabaserid(entry_attrs, options)
- self.obj.handle_iparangetype(entry_attrs, options)
- return dn
-
-
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
deleted file mode 100644
index 537f924ce..000000000
--- a/ipalib/plugins/idviews.py
+++ /dev/null
@@ -1,1123 +0,0 @@
-# Authors:
-# Alexander Bokovoy <abokovoy@redhat.com>
-# Tomas Babej <tbabej@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 re
-
-import six
-
-from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
- LDAPDelete, LDAPUpdate, LDAPSearch,
- LDAPAddAttribute, LDAPRemoveAttribute,
- LDAPRetrieve, global_output_params)
-from .hostgroup import get_complete_hostgroup_member_list
-from .service import validate_certificate
-from ipalib import api, Str, Int, Bytes, Flag, _, ngettext, errors, output
-from ipalib.constants import IPA_ANCHOR_PREFIX, SID_ANCHOR_PREFIX
-from ipalib.plugable import Registry
-from ipalib.util import (normalize_sshpubkey, validate_sshpubkey,
- convert_sshpubkey_post)
-
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-_dcerpc_bindings_installed = False
-
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- import ipaserver.dcerpc
- _dcerpc_bindings_installed = True
- except ImportError:
- pass
-
-__doc__ = _("""
-ID Views
-Manage ID Views
-IPA allows to override certain properties of users and groups per each host.
-This functionality is primarily used to allow migration from older systems or
-other Identity Management solutions.
-""")
-
-register = Registry()
-
-protected_default_trust_view_error = errors.ProtectedEntryError(
- label=_('ID View'),
- key=u"Default Trust View",
- reason=_('system ID View')
-)
-
-fallback_to_ldap_option = Flag(
- 'fallback_to_ldap?',
- default=False,
- label=_('Fallback to AD DC LDAP'),
- doc=_("Allow falling back to AD DC LDAP when resolving AD "
- "trusted objects. For two-way trusts only."),
-)
-
-DEFAULT_TRUST_VIEW_NAME = "default trust view"
-
-ANCHOR_REGEX = re.compile(
- r':IPA:.*:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
- r'|'
- r':SID:S-[0-9\-]+'
-)
-
-
-@register()
-class idview(LDAPObject):
- """
- ID View object.
- """
-
- container_dn = api.env.container_views
- object_name = _('ID View')
- object_name_plural = _('ID Views')
- object_class = ['ipaIDView', 'top']
- default_attributes = ['cn', 'description']
- rdn_is_primary_key = True
-
- label = _('ID Views')
- label_singular = _('ID View')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('ID View Name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- )
-
- permission_filter_objectclasses = ['nsContainer']
- managed_permissions = {
- 'System: Read ID Views': {
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'objectClass',
- },
- },
- }
-
-
-@register()
-class idview_add(LDAPCreate):
- __doc__ = _('Add a new ID View.')
- msg_summary = _('Added ID View "%(value)s"')
-
-
-@register()
-class idview_del(LDAPDelete):
- __doc__ = _('Delete an ID View.')
- msg_summary = _('Deleted ID View "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- for key in keys:
- if key.lower() == DEFAULT_TRUST_VIEW_NAME:
- raise protected_default_trust_view_error
-
- return dn
-
-
-@register()
-class idview_mod(LDAPUpdate):
- __doc__ = _('Modify an ID View.')
- msg_summary = _('Modified an ID View "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- for key in keys:
- if key.lower() == DEFAULT_TRUST_VIEW_NAME:
- raise protected_default_trust_view_error
-
- return dn
-
-
-@register()
-class idview_find(LDAPSearch):
- __doc__ = _('Search for an ID View.')
- msg_summary = ngettext('%(count)d ID View matched',
- '%(count)d ID Views matched', 0)
-
-
-@register()
-class idview_show(LDAPRetrieve):
- __doc__ = _('Display information about an ID View.')
-
- takes_options = LDAPRetrieve.takes_options + (
- Flag('show_hosts?',
- cli_name='show_hosts',
- doc=_('Enumerate all the hosts the view applies to.'),
- ),
- )
-
- has_output_params = global_output_params + (
- Str('useroverrides',
- label=_('User object overrides'),
- ),
- Str('groupoverrides',
- label=_('Group object overrides'),
- ),
- Str('appliedtohosts',
- label=_('Hosts the view applies to')
- ),
- )
-
- def show_id_overrides(self, dn, entry_attrs):
- ldap = self.obj.backend
-
- for objectclass, obj_type in [('ipaUserOverride', 'user'),
- ('ipaGroupOverride', 'group')]:
-
- # Attribute to store results is called (user|group)overrides
- attr_name = obj_type + 'overrides'
-
- try:
- (overrides, truncated) = ldap.find_entries(
- filter="objectclass=%s" % objectclass,
- attrs_list=['ipaanchoruuid'],
- base_dn=dn,
- scope=ldap.SCOPE_ONELEVEL,
- paged_search=True)
-
- resolved_overrides = []
- for override in overrides:
- anchor = override.single_value['ipaanchoruuid']
-
- try:
- name = resolve_anchor_to_object_name(ldap, obj_type,
- anchor)
- resolved_overrides.append(name)
-
- except (errors.NotFound, errors.ValidationError):
- # Anchor could not be resolved, use raw
- resolved_overrides.append(anchor)
-
- entry_attrs[attr_name] = resolved_overrides
-
- except errors.NotFound:
- # No overrides found, nothing to do
- pass
-
- def enumerate_hosts(self, dn, entry_attrs):
- ldap = self.obj.backend
-
- filter_params = {
- 'ipaAssignedIDView': dn,
- 'objectClass': 'ipaHost',
- }
-
- try:
- (hosts, truncated) = ldap.find_entries(
- filter=ldap.make_filter(filter_params, rules=ldap.MATCH_ALL),
- attrs_list=['cn'],
- base_dn=api.env.container_host + api.env.basedn,
- scope=ldap.SCOPE_ONELEVEL,
- paged_search=True)
-
- entry_attrs['appliedtohosts'] = [host.single_value['cn']
- for host in hosts]
- except errors.NotFound:
- pass
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.show_id_overrides(dn, entry_attrs)
-
- # Enumerating hosts is a potentially expensive operation (uses paged
- # search to list all the hosts the ID view applies to). Show the list
- # of the hosts only if explicitly asked for (or asked for --all).
- # Do not display with --raw, since this attribute does not exist in
- # LDAP.
-
- if ((options.get('show_hosts') or options.get('all'))
- and not options.get('raw')):
- self.enumerate_hosts(dn, entry_attrs)
-
- return dn
-
-
-class baseidview_apply(LDAPQuery):
- """
- Base class for idview_apply and idview_unapply commands.
- """
-
- has_output_params = global_output_params
-
- def execute(self, *keys, **options):
- view = keys[-1] if keys else None
- ldap = self.obj.backend
-
- # Test if idview actually exists, if it does not, NotFound is raised
- if not options.get('clear_view', False):
- view_dn = self.api.Object['idview'].get_dn_if_exists(view)
- assert isinstance(view_dn, DN)
-
- # Check that we're not applying the Default Trust View
- if view.lower() == DEFAULT_TRUST_VIEW_NAME:
- raise errors.ValidationError(
- name=_('ID View'),
- error=_('Default Trust View cannot be applied on hosts')
- )
-
- else:
- # In case we are removing assigned view, we modify the host setting
- # the ipaAssignedIDView to None
- view_dn = None
-
- completed = 0
- succeeded = {'host': []}
- failed = {
- 'host': [],
- 'hostgroup': [],
- }
-
- # Make sure we ignore None passed via host or hostgroup, since it does
- # not make sense
- for key in ('host', 'hostgroup'):
- if key in options and options[key] is None:
- del options[key]
-
- # Generate a list of all hosts to apply the view to
- hosts_to_apply = list(options.get('host', []))
-
- for hostgroup in options.get('hostgroup', ()):
- try:
- hosts_to_apply += get_complete_hostgroup_member_list(hostgroup)
- except errors.NotFound:
- failed['hostgroup'].append((hostgroup, unicode(_("not found"))))
- except errors.PublicError as e:
- failed['hostgroup'].append((hostgroup, "%s : %s" % (
- e.__class__.__name__, str(e))))
-
- for host in hosts_to_apply:
- try:
- host_dn = api.Object['host'].get_dn_if_exists(host)
-
- host_entry = ldap.get_entry(host_dn,
- attrs_list=['ipaassignedidview'])
- host_entry['ipaassignedidview'] = view_dn
-
- ldap.update_entry(host_entry)
-
- # If no exception was raised, view assigment went well
- completed = completed + 1
- succeeded['host'].append(host)
- except errors.EmptyModlist:
- # If view was already applied, complain about it
- failed['host'].append((host,
- unicode(_("ID View already applied"))))
- except errors.NotFound:
- failed['host'].append((host, unicode(_("not found"))))
- except errors.PublicError as e:
- failed['host'].append((host, str(e)))
-
- # Wrap dictionary containing failures in another dictionary under key
- # 'memberhost', since that is output parameter in global_output_params
- # and thus we get nice output in the CLI
- failed = {'memberhost': failed}
-
- # Sort the list of affected hosts
- succeeded['host'].sort()
-
- # Note that we're returning the list of affected hosts even if they
- # were passed via referencing a hostgroup. This is desired, since we
- # want to stress the fact that view is applied on all the current
- # member hosts of the hostgroup and not tied with the hostgroup itself.
-
- return dict(
- summary=unicode(_(self.msg_summary % {'value': view})),
- succeeded=succeeded,
- completed=completed,
- failed=failed,
- )
-
-
-@register()
-class idview_apply(baseidview_apply):
- __doc__ = _('Applies ID View to specified hosts or current members of '
- 'specified hostgroups. If any other ID View is applied to '
- 'the host, it is overridden.')
-
- member_count_out = (_('ID View applied to %i host.'),
- _('ID View applied to %i hosts.'))
-
- msg_summary = 'Applied ID View "%(value)s"'
-
- takes_options = (
- Str('host*',
- cli_name='hosts',
- doc=_('Hosts to apply the ID View to'),
- label=_('hosts'),
- ),
- Str('hostgroup*',
- cli_name='hostgroups',
- doc=_('Hostgroups to whose hosts apply the ID View to. Please note '
- 'that view is not applied automatically to any hosts added '
- 'to the hostgroup after running the idview-apply command.'),
- label=_('hostgroups'),
- ),
- )
-
- has_output = (
- output.summary,
- output.Output('succeeded',
- type=dict,
- doc=_('Hosts that this ID View was applied to.'),
- ),
- output.Output('failed',
- type=dict,
- doc=_('Hosts or hostgroups that this ID View could not be '
- 'applied to.'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of hosts the ID View was applied to:'),
- ),
- )
-
-
-@register()
-class idview_unapply(baseidview_apply):
- __doc__ = _('Clears ID View from specified hosts or current members of '
- 'specified hostgroups.')
-
- member_count_out = (_('ID View cleared from %i host.'),
- _('ID View cleared from %i hosts.'))
-
- msg_summary = 'Cleared ID Views'
-
- takes_options = (
- Str('host*',
- cli_name='hosts',
- doc=_('Hosts to clear (any) ID View from.'),
- label=_('hosts'),
- ),
- Str('hostgroup*',
- cli_name='hostgroups',
- doc=_('Hostgroups whose hosts should have ID Views cleared. Note '
- 'that view is not cleared automatically from any host added '
- 'to the hostgroup after running idview-unapply command.'),
- label=_('hostgroups'),
- ),
- )
-
- has_output = (
- output.summary,
- output.Output('succeeded',
- type=dict,
- doc=_('Hosts that ID View was cleared from.'),
- ),
- output.Output('failed',
- type=dict,
- doc=_('Hosts or hostgroups that ID View could not be cleared '
- 'from.'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of hosts that had a ID View was unset:'),
- ),
- )
-
- # Take no arguments, since ID View reference is not needed to clear
- # the hosts
- def get_args(self):
- return ()
-
- def execute(self, *keys, **options):
- options['clear_view'] = True
- return super(idview_unapply, self).execute(*keys, **options)
-
-
-# ID overrides helper methods
-def verify_trusted_domain_object_type(validator, desired_type, name_or_sid):
-
- object_type = validator.get_trusted_domain_object_type(name_or_sid)
-
- if object_type == desired_type:
- # In case SSSD returns the same type as the type being
- # searched, no problems here.
- return True
-
- elif desired_type == 'user' and object_type == 'both':
- # Type both denotes users with magic private groups.
- # Overriding attributes for such users is OK.
- return True
-
- elif desired_type == 'group' and object_type == 'both':
- # However, overriding attributes for magic private groups
- # does not make sense. One should override the GID of
- # the user itself.
-
- raise errors.ConversionError(
- name='identifier',
- error=_('You are trying to reference a magic private group '
- 'which is not allowed to be overridden. '
- 'Try overriding the GID attribute of the '
- 'corresponding user instead.')
- )
-
- return False
-
-
-def resolve_object_to_anchor(ldap, obj_type, obj, fallback_to_ldap):
- """
- Resolves the user/group name to the anchor uuid:
- - first it tries to find the object as user or group in IPA (depending
- on the passed obj_type)
- - if the IPA lookup failed, lookup object SID in the trusted domains
-
- Takes options:
- ldap - the backend
- obj_type - either 'user' or 'group'
- obj - the name of the object, e.g 'admin' or 'testuser'
- """
-
- try:
- entry = ldap.get_entry(api.Object[obj_type].get_dn(obj),
- attrs_list=['ipaUniqueID', 'objectClass'])
-
- # First we check this is a valid object to override
- # - for groups, it must have ipaUserGroup objectclass
- # - for users, it must have posixAccount objectclass
-
- required_objectclass = {
- 'user': 'posixaccount',
- 'group': 'ipausergroup',
- }[obj_type]
-
- if required_objectclass not in entry['objectclass']:
- raise errors.ValidationError(
- name=_('IPA object'),
- error=_('system IPA objects (e.g system groups, user '
- 'private groups) cannot be overridden')
- )
-
- # The domain prefix, this will need to be reworked once we
- # introduce IPA-IPA trusts
- domain = api.env.domain
- uuid = entry.single_value['ipaUniqueID']
-
- return "%s%s:%s" % (IPA_ANCHOR_PREFIX, domain, uuid)
- except errors.NotFound:
- pass
-
- # If not successfull, try looking up the object in the trusted domain
- try:
- if _dcerpc_bindings_installed:
- domain_validator = ipaserver.dcerpc.DomainValidator(api)
- if domain_validator.is_configured():
- sid = domain_validator.get_trusted_domain_object_sid(obj,
- fallback_to_ldap=fallback_to_ldap)
-
- # We need to verify that the object type is correct
- type_correct = verify_trusted_domain_object_type(
- domain_validator, obj_type, sid)
-
- if type_correct:
- # There is no domain prefix since SID contains information
- # about the domain
- return SID_ANCHOR_PREFIX + sid
-
- except errors.ValidationError:
- # Domain validator raises Validation Error if object name does not
- # contain domain part (either NETBIOS\ prefix or @domain.name suffix)
- pass
-
- # No acceptable object was found
- api.Object[obj_type].handle_not_found(obj)
-
-
-def resolve_anchor_to_object_name(ldap, obj_type, anchor):
- """
- Resolves IPA Anchor UUID to the actual common object name (uid for users,
- cn for groups).
-
- Takes options:
- ldap - the backend
- anchor - the anchor, e.g.
- ':IPA:ipa.example.com:2cb604ea-39a5-11e4-a37e-001a4a22216f'
- """
-
- if anchor.startswith(IPA_ANCHOR_PREFIX):
-
- # Prepare search parameters
- accounts_dn = DN(api.env.container_accounts, api.env.basedn)
-
- # Anchor of the form :IPA:<domain>:<uuid>
- # Strip the IPA prefix and the domain prefix
- uuid = anchor.rpartition(':')[-1].strip()
-
- # Set the object type-specific search attributes
- objectclass, name_attr = {
- 'user': ('posixaccount', 'uid'),
- 'group': ('ipausergroup', 'cn'),
- }[obj_type]
-
- entry = ldap.find_entry_by_attr(attr='ipaUniqueID',
- value=uuid,
- object_class=objectclass,
- attrs_list=[name_attr],
- base_dn=accounts_dn)
-
- # Return the name of the object, which is either cn for
- # groups or uid for users
- return entry.single_value[name_attr]
-
- elif anchor.startswith(SID_ANCHOR_PREFIX):
-
- # Parse the SID out from the anchor
- sid = anchor[len(SID_ANCHOR_PREFIX):].strip()
-
- if _dcerpc_bindings_installed:
- domain_validator = ipaserver.dcerpc.DomainValidator(api)
- if domain_validator.is_configured():
- name = domain_validator.get_trusted_domain_object_from_sid(sid)
-
- # We need to verify that the object type is correct
- type_correct = verify_trusted_domain_object_type(
- domain_validator, obj_type, name)
-
- if type_correct:
- return name
-
- # No acceptable object was found
- raise errors.NotFound(
- reason=_("Anchor '%(anchor)s' could not be resolved.")
- % dict(anchor=anchor))
-
-
-def remove_ipaobject_overrides(ldap, api, dn):
- """
- Removes all ID overrides for given object. This method is to be
- consumed by -del commands of the given objects (users, groups).
- """
-
- entry = ldap.get_entry(dn, attrs_list=['ipaUniqueID'])
- object_uuid = entry.single_value['ipaUniqueID']
-
- override_filter = '(ipaanchoruuid=:IPA:{0}:{1})'.format(api.env.domain,
- object_uuid)
- try:
- entries, truncated = ldap.find_entries(
- override_filter,
- base_dn=DN(api.env.container_views, api.env.basedn),
- paged_search=True
- )
- except errors.EmptyResult:
- pass
- else:
- # In case we found something, delete it
- for entry in entries:
- ldap.delete_entry(entry)
-
-
-# This is not registered on purpose, it's a base class for ID overrides
-class baseidoverride(LDAPObject):
- """
- Base ID override object.
- """
-
- parent_object = 'idview'
- container_dn = api.env.container_views
-
- object_class = ['ipaOverrideAnchor', 'top']
- default_attributes = [
- 'description', 'ipaAnchorUUID',
- ]
-
- takes_params = (
- Str('ipaanchoruuid',
- cli_name='anchor',
- primary_key=True,
- label=_('Anchor to override'),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- )
-
- override_object = None
-
- def get_dn(self, *keys, **options):
- # If user passed raw anchor, do not try
- # to translate it.
- if ANCHOR_REGEX.match(keys[-1]):
- anchor = keys[-1]
-
- # Otherwise, translate object into a
- # legitimate object anchor.
- else:
- anchor = resolve_object_to_anchor(
- self.backend,
- self.override_object,
- keys[-1],
- fallback_to_ldap=options['fallback_to_ldap']
- )
-
- keys = keys[:-1] + (anchor, )
- return super(baseidoverride, self).get_dn(*keys, **options)
-
- def set_anchoruuid_from_dn(self, dn, entry_attrs):
- # TODO: Use entry_attrs.single_value once LDAPUpdate supports
- # lists in primary key fields (baseldap.LDAPUpdate.execute)
- entry_attrs['ipaanchoruuid'] = dn[0].value
-
- def convert_anchor_to_human_readable_form(self, entry_attrs, **options):
- if not options.get('raw'):
- anchor = entry_attrs.single_value['ipaanchoruuid']
-
- if anchor:
- try:
- object_name = resolve_anchor_to_object_name(
- self.backend,
- self.override_object,
- anchor
- )
- entry_attrs.single_value['ipaanchoruuid'] = object_name
- except errors.NotFound:
- # If we were unable to resolve the anchor,
- # keep it in the raw form
- pass
- except errors.ValidationError:
- # Same as above, ValidationError may be raised when SIDs
- # are attempted to be converted, but the domain is no
- # longer trusted
- pass
-
- def prohibit_ipa_users_in_default_view(self, dn, entry_attrs):
- # Check if parent object is Default Trust View, if so, prohibit
- # adding overrides for IPA objects
-
- if dn[1].value.lower() == DEFAULT_TRUST_VIEW_NAME:
- if dn[0].value.startswith(IPA_ANCHOR_PREFIX):
- raise errors.ValidationError(
- name=_('ID View'),
- error=_('Default Trust View cannot contain IPA users')
- )
-
-class baseidoverride_add(LDAPCreate):
- __doc__ = _('Add a new ID override.')
- msg_summary = _('Added ID override "%(value)s"')
-
- takes_options = LDAPCreate.takes_options + (fallback_to_ldap_option,)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- self.obj.set_anchoruuid_from_dn(dn, entry_attrs)
- self.obj.prohibit_ipa_users_in_default_view(dn, entry_attrs)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
- return dn
-
-
-class baseidoverride_del(LDAPDelete):
- __doc__ = _('Delete an ID override.')
- msg_summary = _('Deleted ID override "%(value)s"')
-
- takes_options = LDAPDelete.takes_options + (fallback_to_ldap_option,)
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
-
- # Make sure the entry we're deleting has all the objectclasses
- # this object requires
- try:
- entry = ldap.get_entry(dn, ['objectclass'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- required_object_classes = set(self.obj.object_class)
- actual_object_classes = set(entry['objectclass'])
-
- # If not, treat it as a failed search
- if not required_object_classes.issubset(actual_object_classes):
- self.obj.handle_not_found(*keys)
-
- return dn
-
-
-class baseidoverride_mod(LDAPUpdate):
- __doc__ = _('Modify an ID override.')
- msg_summary = _('Modified an ID override "%(value)s"')
-
- takes_options = LDAPUpdate.takes_options + (fallback_to_ldap_option,)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- if 'rename' in options:
- raise errors.ValidationError(
- name=_('ID override'),
- error=_('ID overrides cannot be renamed')
- )
-
- self.obj.prohibit_ipa_users_in_default_view(dn, entry_attrs)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
- return dn
-
-
-class baseidoverride_find(LDAPSearch):
- __doc__ = _('Search for an ID override.')
- msg_summary = ngettext('%(count)d ID override matched',
- '%(count)d ID overrides matched', 0)
-
- takes_options = LDAPSearch.takes_options + (fallback_to_ldap_option,)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for entry in entries:
- self.obj.convert_anchor_to_human_readable_form(entry, **options)
- return truncated
-
-
-class baseidoverride_show(LDAPRetrieve):
- __doc__ = _('Display information about an ID override.')
-
- takes_options = LDAPRetrieve.takes_options + (fallback_to_ldap_option,)
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
- return dn
-
-
-@register()
-class idoverrideuser(baseidoverride):
-
- object_name = _('User ID override')
- object_name_plural = _('User ID overrides')
-
- label = _('User ID overrides')
- label_singular = _('User ID override')
- rdn_is_primary_key = True
-
- permission_filter_objectclasses = ['ipaUserOverride']
- managed_permissions = {
- 'System: Read User ID Overrides': {
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectClass', 'ipaAnchorUUID', 'uidNumber', 'description',
- 'homeDirectory', 'uid', 'ipaOriginalUid', 'loginShell', 'gecos',
- 'gidNumber', 'ipaSshPubkey', 'usercertificate'
- },
- },
- }
-
- object_class = baseidoverride.object_class + ['ipaUserOverride']
- possible_objectclasses = ['ipasshuser', 'ipaSshGroupOfPubKeys']
- default_attributes = baseidoverride.default_attributes + [
- 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
- 'ipaSshPubkey', 'gidNumber', 'gecos', 'usercertificate;binary',
- ]
-
- search_display_attributes = baseidoverride.default_attributes + [
- 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell',
- 'ipaSshPubkey', 'gidNumber', 'gecos',
- ]
-
- takes_params = baseidoverride.takes_params + (
- Str('uid?',
- pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
- pattern_errmsg='may only include letters, numbers, _, -, . and $',
- maxlength=255,
- cli_name='login',
- label=_('User login'),
- normalizer=lambda value: value.lower(),
- ),
- Int('uidnumber?',
- cli_name='uid',
- label=_('UID'),
- doc=_('User ID Number'),
- minvalue=1,
- ),
- Str('gecos?',
- label=_('GECOS'),
- ),
- Int('gidnumber?',
- label=_('GID'),
- doc=_('Group ID Number'),
- minvalue=1,
- ),
- Str('homedirectory?',
- cli_name='homedir',
- label=_('Home directory'),
- ),
- Str('loginshell?',
- cli_name='shell',
- label=_('Login shell'),
- ),
- Str('ipaoriginaluid?',
- flags=['no_option', 'no_output']
- ),
- Str('ipasshpubkey*', validate_sshpubkey,
- cli_name='sshpubkey',
- label=_('SSH public key'),
- normalizer=normalize_sshpubkey,
- flags=['no_search'],
- ),
- Bytes('usercertificate*', validate_certificate,
- cli_name='certificate',
- label=_('Certificate'),
- doc=_('Base-64 encoded user certificate'),
- flags=['no_search',],
- ),
- )
-
- override_object = 'user'
-
- def update_original_uid_reference(self, entry_attrs):
- anchor = entry_attrs.single_value['ipaanchoruuid']
- try:
- original_uid = resolve_anchor_to_object_name(self.backend,
- self.override_object,
- anchor)
- entry_attrs['ipaOriginalUid'] = original_uid
-
- except (errors.NotFound, errors.ValidationError):
- # Anchor could not be resolved, this means we had to specify the
- # object to manipulate using a raw anchor value already, hence
- # we have no way to update the original_uid
- pass
-
- def convert_usercertificate_pre(self, entry_attrs):
- if 'usercertificate' in entry_attrs:
- entry_attrs['usercertificate;binary'] = entry_attrs.pop(
- 'usercertificate')
-
- def convert_usercertificate_post(self, entry_attrs, **options):
- if 'usercertificate;binary' in entry_attrs:
- entry_attrs['usercertificate'] = entry_attrs.pop(
- 'usercertificate;binary')
-
-
-
-@register()
-class idoverridegroup(baseidoverride):
-
- object_name = _('Group ID override')
- object_name_plural = _('Group ID overrides')
-
- label = _('Group ID overrides')
- label_singular = _('Group ID override')
- rdn_is_primary_key = True
-
- permission_filter_objectclasses = ['ipaGroupOverride']
- managed_permissions = {
- 'System: Read Group ID Overrides': {
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectClass', 'ipaAnchorUUID', 'gidNumber',
- 'description', 'cn',
- },
- },
- }
-
- object_class = baseidoverride.object_class + ['ipaGroupOverride']
- default_attributes = baseidoverride.default_attributes + [
- 'gidNumber', 'cn',
- ]
-
- takes_params = baseidoverride.takes_params + (
- Str('cn?',
- pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
- pattern_errmsg='may only include letters, numbers, _, -, . and $',
- maxlength=255,
- cli_name='group_name',
- label=_('Group name'),
- normalizer=lambda value: value.lower(),
- ),
- Int('gidnumber?',
- cli_name='gid',
- label=_('GID'),
- doc=_('Group ID Number'),
- minvalue=1,
- ),
- )
-
- override_object = 'group'
-
-@register()
-class idoverrideuser_add_cert(LDAPAddAttribute):
- __doc__ = _('Add one or more certificates to the idoverrideuser entry')
- msg_summary = _('Added certificates to idoverrideuser "%(value)s"')
- attribute = 'usercertificate'
-
- takes_options = LDAPAddAttribute.takes_options + (fallback_to_ldap_option,)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- dn = self.obj.get_dn(*keys, **options)
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
- return dn
-
-
-@register()
-class idoverrideuser_remove_cert(LDAPRemoveAttribute):
- __doc__ = _('Remove one or more certificates to the idoverrideuser entry')
- msg_summary = _('Removed certificates from idoverrideuser "%(value)s"')
- attribute = 'usercertificate'
-
- takes_options = LDAPRemoveAttribute.takes_options + (fallback_to_ldap_option,)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- dn = self.obj.get_dn(*keys, **options)
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- self.obj.convert_anchor_to_human_readable_form(entry_attrs, **options)
-
- return dn
-
-
-@register()
-class idoverrideuser_add(baseidoverride_add):
- __doc__ = _('Add a new User ID override.')
- msg_summary = _('Added User ID override "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = super(idoverrideuser_add, self).pre_callback(ldap, dn,
- entry_attrs, attrs_list, *keys, **options)
-
- entry_attrs['objectclass'].append('ipasshuser')
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- # Update the ipaOriginalUid
- self.obj.update_original_uid_reference(entry_attrs)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- dn = super(idoverrideuser_add, self).post_callback(ldap, dn,
- entry_attrs, *keys, **options)
- convert_sshpubkey_post(entry_attrs)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- return dn
-
-
-
-@register()
-class idoverrideuser_del(baseidoverride_del):
- __doc__ = _('Delete an User ID override.')
- msg_summary = _('Deleted User ID override "%(value)s"')
-
-
-@register()
-class idoverrideuser_mod(baseidoverride_mod):
- __doc__ = _('Modify an User ID override.')
- msg_summary = _('Modified an User ID override "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = super(idoverrideuser_mod, self).pre_callback(ldap, dn,
- entry_attrs, attrs_list, *keys, **options)
-
- # Update the ipaOriginalUid
- self.obj.set_anchoruuid_from_dn(dn, entry_attrs)
- self.obj.update_original_uid_reference(entry_attrs)
- if 'objectclass' in entry_attrs:
- obj_classes = entry_attrs['objectclass']
- else:
- _entry_attrs = ldap.get_entry(dn, ['objectclass'])
- obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass']
-
- if 'ipasshpubkey' in entry_attrs and 'ipasshuser' not in obj_classes:
- obj_classes.append('ipasshuser')
-
- self.obj.convert_usercertificate_pre(entry_attrs)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- dn = super(idoverrideuser_mod, self).post_callback(ldap, dn,
- entry_attrs, *keys, **options)
- convert_sshpubkey_post(entry_attrs)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- return dn
-
-
-@register()
-class idoverrideuser_find(baseidoverride_find):
- __doc__ = _('Search for an User ID override.')
- msg_summary = ngettext('%(count)d User ID override matched',
- '%(count)d User ID overrides matched', 0)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- truncated = super(idoverrideuser_find, self).post_callback(
- ldap, entries, truncated, *args, **options)
- for entry in entries:
- convert_sshpubkey_post(entry)
- self.obj.convert_usercertificate_post(entry, **options)
- return truncated
-
-
-@register()
-class idoverrideuser_show(baseidoverride_show):
- __doc__ = _('Display information about an User ID override.')
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- dn = super(idoverrideuser_show, self).post_callback(ldap, dn,
- entry_attrs, *keys, **options)
- convert_sshpubkey_post(entry_attrs)
- self.obj.convert_usercertificate_post(entry_attrs, **options)
- return dn
-
-
-@register()
-class idoverridegroup_add(baseidoverride_add):
- __doc__ = _('Add a new Group ID override.')
- msg_summary = _('Added Group ID override "%(value)s"')
-
-
-@register()
-class idoverridegroup_del(baseidoverride_del):
- __doc__ = _('Delete an Group ID override.')
- msg_summary = _('Deleted Group ID override "%(value)s"')
-
-
-@register()
-class idoverridegroup_mod(baseidoverride_mod):
- __doc__ = _('Modify an Group ID override.')
- msg_summary = _('Modified an Group ID override "%(value)s"')
-
-
-@register()
-class idoverridegroup_find(baseidoverride_find):
- __doc__ = _('Search for an Group ID override.')
- msg_summary = ngettext('%(count)d Group ID override matched',
- '%(count)d Group ID overrides matched', 0)
-
-
-@register()
-class idoverridegroup_show(baseidoverride_show):
- __doc__ = _('Display information about an Group ID override.')
diff --git a/ipalib/plugins/internal.py b/ipalib/plugins/internal.py
deleted file mode 100644
index 99b0c04d1..000000000
--- a/ipalib/plugins/internal.py
+++ /dev/null
@@ -1,859 +0,0 @@
-# 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/>.
-
-"""
-Plugins not accessible directly through the CLI, commands used internally
-"""
-from ipalib import Command
-from ipalib import Str
-from ipalib.output import Output
-from ipalib.text import _
-from ipalib.util import json_serialize
-from ipalib.plugable import Registry
-
-register = Registry()
-
-@register()
-class json_metadata(Command):
- """
- Export plugin meta-data for the webUI.
- """
- NO_CLI = True
-
-
- takes_args = (
- Str('objname?',
- doc=_('Name of object to export'),
- ),
- Str('methodname?',
- doc=_('Name of method to export'),
- ),
- )
-
- takes_options = (
- Str('object?',
- doc=_('Name of object to export'),
- ),
- Str('method?',
- doc=_('Name of method to export'),
- ),
- Str('command?',
- doc=_('Name of command to export'),
- ),
- )
-
- has_output = (
- Output('objects', dict, doc=_('Dict of JSON encoded IPA Objects')),
- Output('methods', dict, doc=_('Dict of JSON encoded IPA Methods')),
- Output('commands', dict, doc=_('Dict of JSON encoded IPA Commands')),
- )
-
- def execute(self, objname=None, methodname=None, **options):
- objects = dict()
- methods = dict()
- commands = dict()
-
- empty = True
-
- try:
- if not objname:
- objname = options['object']
- if objname in self.api.Object:
- o = self.api.Object[objname]
- objects = dict([(o.name, json_serialize(o))])
- elif objname == "all":
- objects = dict(
- (o.name, json_serialize(o)) for o in self.api.Object()
- )
- empty = False
- except KeyError:
- pass
-
- try:
- if not methodname:
- methodname = options['method']
- if methodname in self.api.Method:
- m = self.api.Method[methodname]
- methods = dict([(m.name, json_serialize(m))])
- elif methodname == "all":
- methods = dict(
- (m.name, json_serialize(m)) for m in self.api.Method()
- )
- empty = False
- except KeyError:
- pass
-
- try:
- cmdname = options['command']
- if cmdname in self.api.Command:
- c = self.api.Command[cmdname]
- commands = dict([(c.name, json_serialize(c))])
- elif cmdname == "all":
- commands = dict(
- (c.name, json_serialize(c)) for c in self.api.Command()
- )
- empty = False
- except KeyError:
- pass
-
- if empty:
- objects = dict(
- (o.name, json_serialize(o)) for o in self.api.Object()
- )
- methods = dict(
- (m.name, json_serialize(m)) for m in self.api.Method()
- )
- commands = dict(
- (c.name, json_serialize(c)) for c in self.api.Command()
- )
-
- retval = dict([
- ("objects", objects),
- ("methods", methods),
- ("commands", commands),
- ])
-
- return retval
-
-
-@register()
-class i18n_messages(Command):
- NO_CLI = True
-
- messages = {
- "ajax": {
- "401": {
- "message": _("Your session has expired. Please re-login."),
- },
- },
- "actions": {
- "apply": _("Apply"),
- "automember_rebuild": _("Rebuild auto membership"),
- "automember_rebuild_confirm": _("Are you sure you want to rebuild auto membership?"),
- "automember_rebuild_success": _("Automember rebuild membership task completed"),
- "confirm": _("Are you sure you want to proceed with the action?"),
- "delete_confirm": _("Are you sure you want to delete ${object}?"),
- "disable_confirm": _("Are you sure you want to disable ${object}?"),
- "enable_confirm": _("Are you sure you want to enable ${object}?"),
- "title": _("Actions"),
- },
- "association": {
- "add": {
- "ipasudorunas": _("Add RunAs ${other_entity} into ${entity} ${primary_key}"),
- "ipasudorunasgroup": _("Add RunAs Groups into ${entity} ${primary_key}"),
- "managedby": _("Add ${other_entity} Managing ${entity} ${primary_key}"),
- "member": _("Add ${other_entity} into ${entity} ${primary_key}"),
- "memberallowcmd": _("Add Allow ${other_entity} into ${entity} ${primary_key}"),
- "memberdenycmd": _("Add Deny ${other_entity} into ${entity} ${primary_key}"),
- "memberof": _("Add ${entity} ${primary_key} into ${other_entity}"),
- },
- "added": _("${count} item(s) added"),
- "direct_membership": _("Direct Membership"),
- "filter_placeholder": _("Filter available ${other_entity}"),
- "indirect_membership": _("Indirect Membership"),
- "no_entries": _("No entries."),
- "paging": _("Showing ${start} to ${end} of ${total} entries."),
- "remove": {
- "ipasudorunas": _("Remove RunAs ${other_entity} from ${entity} ${primary_key}"),
- "ipasudorunasgroup": _("Remove RunAs Groups from ${entity} ${primary_key}"),
- "managedby": _("Remove ${other_entity} Managing ${entity} ${primary_key}"),
- "member": _("Remove ${other_entity} from ${entity} ${primary_key}"),
- "memberallowcmd": _("Remove Allow ${other_entity} from ${entity} ${primary_key}"),
- "memberdenycmd": _("Remove Deny ${other_entity} from ${entity} ${primary_key}"),
- "memberof": _("Remove ${entity} ${primary_key} from ${other_entity}"),
- },
- "removed": _("${count} item(s) removed"),
- "show_results": _("Show Results"),
- },
- "authtype": {
- "config_tooltip": _("<p>Implicit method (password) will be used if no method is chosen.</p><p><strong>Password + Two-factor:</strong> LDAP and Kerberos allow authentication with either one of the authentication types but Kerberos uses pre-authentication method which requires to use armor ccache.</p><p><strong>RADIUS with another type:</strong> Kerberos always use RADIUS, but LDAP never does. LDAP only recognize the password and two-factor authentication options.</p>"),
- "type_otp": _("Two factor authentication (password + OTP)"),
- "type_password": _("Password"),
- "type_radius": _("Radius"),
- "type_disabled": _("Disable per-user override"),
- "user_tooltip": _("<p>Per-user setting, overwrites the global setting if any option is checked.</p><p><strong>Password + Two-factor:</strong> LDAP and Kerberos allow authentication with either one of the authentication types but Kerberos uses pre-authentication method which requires to use armor ccache.</p><p><strong>RADIUS with another type:</strong> Kerberos always use RADIUS, but LDAP never does. LDAP only recognize the password and two-factor authentication options.</p>"),
- },
- "buttons": {
- "about": _("About"),
- "activate": _("Activate"),
- "add": _("Add"),
- "add_and_add_another": _("Add and Add Another"),
- "add_and_close": _("Add and Close"),
- "add_and_edit": _("Add and Edit"),
- "add_many": _("Add Many"),
- "apply": _("Apply"),
- "back": _("Back"),
- "cancel": _("Cancel"),
- "close": _("Close"),
- "disable": _("Disable"),
- "edit": _("Edit"),
- "enable": _("Enable"),
- "filter": _("Filter"),
- "find": _("Find"),
- "get": _("Get"),
- "hide": _("Hide"),
- "issue": _("Issue"),
- "ok": _("OK"),
- "refresh": _("Refresh"),
- "refresh_title": _("Reload current settings from the server."),
- "remove": _("Delete"),
- "reset": _("Reset"),
- "reset_password_and_login": _("Reset Password and Login"),
- "restore": _("Restore"),
- "retry": _("Retry"),
- "revert": _("Revert"),
- "revert_title": ("Undo all unsaved changes."),
- "revoke": _("Revoke"),
- "save": _("Save"),
- "set": _("Set"),
- "show": _("Show"),
- "unapply": ("Un-apply"),
- "update": _("Update"),
- "view": _("View"),
- },
- "details": {
- "collapse_all": _("Collapse All"),
- "expand_all": _("Expand All"),
- "general": _("General"),
- "identity": _("Identity Settings"),
- "settings": _("${entity} ${primary_key} Settings"),
- "to_top": _("Back to Top"),
- "updated": _("${entity} ${primary_key} updated"),
- },
- "dialogs": {
- "add_confirmation": _("${entity} successfully added"),
- "add_title": _("Add ${entity}"),
- "available": _("Available"),
- "batch_error_message": _("Some operations failed."),
- "batch_error_title": _("Operations Error"),
- "confirmation": _("Confirmation"),
- "dirty_message": _("This page has unsaved changes. Please save or revert."),
- "dirty_title": _("Unsaved Changes"),
- "edit_title": _("Edit ${entity}"),
- "hide_details": _("Hide details"),
- "about_title": _("About"),
- "about_message": _("${product}, version: ${version}"),
- "prospective": _("Prospective"),
- "redirection": _("Redirection"),
- "remove_empty": _("Select entries to be removed."),
- "remove_title": _("Remove ${entity}"),
- "result": _("Result"),
- "show_details": _("Show details"),
- "success": _("Success"),
- "validation_title": _("Validation error"),
- "validation_message": _("Input form contains invalid or missing values."),
- },
- "error_report": {
- "options": _("Please try the following options:"),
- "problem_persists": _("If the problem persists please contact the system administrator."),
- "refresh": _("Refresh the page."),
- "reload": _("Reload the browser."),
- "main_page": _("Return to the main page and retry the operation"),
- "title": _("An error has occurred (${error})"),
- },
- "errors": {
- "error": _("Error"),
- "http_error": _("HTTP Error"),
- "internal_error": _("Internal Error"),
- "ipa_error": _("IPA Error"),
- "no_response": _("No response"),
- "unknown_error": _("Unknown Error"),
- "url": _("URL"),
- },
- "facet_groups": {
- "managedby": _("${primary_key} is managed by:"),
- "member": _("${primary_key} members:"),
- "memberof": _("${primary_key} is a member of:"),
- },
- "facets": {
- "details": _("Settings"),
- "search": _("Search"),
- },
- "false": _("False"),
- "keytab": {
- "add_create": _("Allow ${other_entity} to create keytab of ${primary_key}"),
- "add_retrive": _("Allow ${other_entity} to retrieve keytab of ${primary_key}"),
- "allowed_to_create": _("Allowed to create keytab"),
- "allowed_to_retrieve": _("Allowed to retrieve keytab"),
- "remove_create": _("Disallow ${other_entity} to create keytab of ${primary_key}"),
- "remove_retrieve": _("Disallow ${other_entity} to retrieve keytab of ${primary_key}"),
- },
- "krbauthzdata": {
- "inherited": _("Inherited from server configuration"),
- "mspac": _("MS-PAC"),
- "override": _("Override inherited settings"),
- "pad": _("PAD"),
- },
- "login": {
- "form_auth": _("<i class=\"fa fa-info-circle\"></i> To login with <strong>username and password</strong>, enter them in the corresponding fields, then click Login."),
- "header": _("Logged In As"),
- "krb_auth_msg": _("<i class=\"fa fa-info-circle\"></i> To login with <strong>Kerberos</strong>, please make sure you have valid tickets (obtainable via kinit) and <a href='http://${host}/ipa/config/unauthorized.html'>configured</a> the browser correctly, then click Login."),
- "login": _("Login"),
- "logout": _("Logout"),
- "logout_error": _("Logout error"),
- "password": _("Password"),
- "sync_otp_token": _("Sync OTP Token"),
- "username": _("Username"),
- },
- "measurement_units": {
- "number_of_passwords": _("number of passwords"),
- "seconds": _("seconds"),
- },
- "objects": {
- "aci": {
- "attribute": _("Attribute"),
- },
- "automember": {
- "add_condition": _("Add Condition into ${pkey}"),
- "add_rule": _("Add Rule"),
- "attribute": _("Attribute"),
- "default_host_group": _("Default host group"),
- "default_user_group": _("Default user group"),
- "exclusive": _("Exclusive"),
- "expression": _("Expression"),
- "hostgrouprule": _("Host group rule"),
- "hostgrouprules": _("Host group rules"),
- "inclusive": _("Inclusive"),
- "usergrouprule": _("User group rule"),
- "usergrouprules": _("User group rules"),
- },
- "automountkey": {
- },
- "automountlocation": {
- "identity": _("Automount Location Settings")
- },
- "automountmap": {
- "map_type": _("Map Type"),
- "direct": _("Direct"),
- "indirect": _("Indirect"),
- },
- "caacl": {
- "any_host": _("Any Host"),
- "any_service": _("Any Service"),
- "any_profile": _("Any Profile"),
- "anyone": _("Anyone"),
- "ipaenabledflag": _("Rule status"),
- "profile": _("Profiles"),
- "specified_hosts": _("Specified Hosts and Groups"),
- "specified_profiles": _("Specified Profiles"),
- "specified_services": _("Specified Services and Groups"),
- "specified_users": _("Specified Users and Groups"),
- "who": _("Permitted to have certificates issued"),
- },
- "cert": {
- "aa_compromise": _("AA Compromise"),
- "add_principal": _("Add principal"),
- "affiliation_changed": _("Affiliation Changed"),
- "ca_compromise": _("CA Compromise"),
- "certificate": _("Certificate"),
- "certificates": _("Certificates"),
- "certificate_hold": _("Certificate Hold"),
- "cessation_of_operation": _("Cessation of Operation"),
- "common_name": _("Common Name"),
- "expires_on": _("Expires On"),
- "find_issuedon_from": _("Issued on from"),
- "find_issuedon_to": _("Issued on to"),
- "find_max_serial_number": _("Maximum serial number"),
- "find_min_serial_number": _("Minimum serial number"),
- "find_revocation_reason": _("Revocation reason"),
- "find_revokedon_from": _("Revoked on from"),
- "find_revokedon_to": _("Revoked on to"),
- "find_subject": _("Subject"),
- "find_validnotafter_from": _("Valid not after from"),
- "find_validnotafter_to": _("Valid not after to"),
- "find_validnotbefore_from": _("Valid not before from"),
- "find_validnotbefore_to": _("Valid not before to"),
- "fingerprints": _("Fingerprints"),
- "get_certificate": _("Get Certificate"),
- "issue_certificate": _("Issue New Certificate for ${entity} ${primary_key}"),
- "issue_certificate_generic": _("Issue New Certificate"),
- "issued_by": _("Issued By"),
- "issued_on": _("Issued On"),
- "issued_to": _("Issued To"),
- "key_compromise": _("Key Compromise"),
- "md5_fingerprint": _("MD5 Fingerprint"),
- "missing": _("No Valid Certificate"),
- "new_certificate": _("New Certificate"),
- "note": _("Note"),
- "organization": _("Organization"),
- "organizational_unit": _("Organizational Unit"),
- "present": _("${count} certificate(s) present"),
- "privilege_withdrawn": _("Privilege Withdrawn"),
- "reason": _("Reason for Revocation"),
- "remove_from_crl": _("Remove from CRL"),
- "request_message": _("<ol> <li>Create a certificate database or use an existing one. To create a new database:<br/> <code># certutil -N -d &lt;database path&gt;</code> </li> <li>Create a CSR with subject <em>CN=&lt;${cn_name}&gt;,O=&lt;realm&gt;</em>, for example:<br/> <code># certutil -R -d &lt;database path&gt; -a -g &lt;key size&gt; -s 'CN=${cn},O=${realm}'</code> </li> <li> Copy and paste the CSR (from <em>-----BEGIN NEW CERTIFICATE REQUEST-----</em> to <em>-----END NEW CERTIFICATE REQUEST-----</em>) into the text area below: </li> </ol>"),
- "requested": _("Certificate requested"),
- "restore_certificate": _("Restore Certificate for ${entity} ${primary_key}"),
- "restore_certificate_simple": _("Restore Certificate"),
- "restore_confirmation": _("To confirm your intention to restore this certificate, click the \"Restore\" button."),
- "restored": _("Certificate restored"),
- "revocation_reason": _("Revocation reason"),
- "revoke_certificate": _("Revoke Certificate for ${entity} ${primary_key}"),
- "revoke_certificate_simple": _("Revoke Certificate"),
- "revoke_confirmation": _("To confirm your intention to revoke this certificate, select a reason from the pull-down list, and click the \"Revoke\" button."),
- "revoked": _("Certificate Revoked"),
- "serial_number": _("Serial Number"),
- "serial_number_hex": _("Serial Number (hex)"),
- "sha1_fingerprint": _("SHA1 Fingerprint"),
- "status": _("Status"),
- "superseded": _("Superseded"),
- "unspecified": _("Unspecified"),
- "valid": _("Valid Certificate Present"),
- "validity": _("Validity"),
- "view_certificate": _("Certificate for ${entity} ${primary_key}"),
- "view_certificate_btn": _("View Certificate"),
- },
- "config": {
- "group": _("Group Options"),
- "search": _("Search Options"),
- "selinux": _("SELinux Options"),
- "service": _("Service Options"),
- "user": _("User Options"),
- },
- "delegation": {
- },
- "dnsconfig": {
- "forward_first": _("Forward first"),
- "forward_none": _("Forwarding disabled"),
- "forward_only": _("Forward only"),
- "options": _("Options"),
- },
- "dnsrecord": {
- "data": _("Data"),
- "deleted_no_data": _("DNS record was deleted because it contained no data."),
- "other": _("Other Record Types"),
- "ptr_redir_address_err": _("Address not valid, can't redirect"),
- "ptr_redir_create": _("Create dns record"),
- "ptr_redir_creating": _("Creating record."),
- "ptr_redir_creating_err": _("Record creation failed."),
- "ptr_redir_record": _("Checking if record exists."),
- "ptr_redir_record_err": _("Record not found."),
- "ptr_redir_title": _("Redirection to PTR record"),
- "ptr_redir_zone": _("Zone found: ${zone}"),
- "ptr_redir_zone_err": _("Target reverse zone not found."),
- "ptr_redir_zones": _("Fetching DNS zones."),
- "ptr_redir_zones_err": _("An error occurred while fetching dns zones."),
- "redirection_dnszone": _("You will be redirected to DNS Zone."),
- "standard": _("Standard Record Types"),
- "title": _("Records for DNS Zone"),
- "type": _("Record Type"),
- },
- "dnszone": {
- "identity": _("DNS Zone Settings"),
- "add_permission":_("Add Permission"),
- "add_permission_confirm":_("Are you sure you want to add permission for DNS Zone ${object}?"),
- "remove_permission": _("Remove Permission"),
- "remove_permission_confirm": _("Are you sure you want to remove permission for DNS Zone ${object}?"),
- "skip_dns_check": _("Skip DNS check"),
- "skip_overlap_check": _("Skip overlap check"),
- "soamname_change_message": _("Do you want to check if new authoritative nameserver address is in DNS"),
- "soamname_change_title": _("Authoritative nameserver change"),
- },
- "domainlevel": {
- "label": _("Domain Level"),
- "label_singular": _("Domain Level"),
- "ipadomainlevel": _("Level"),
- "set": _("Set Domain Level"),
- },
- "group": {
- "details": _("Group Settings"),
- "external": _("External"),
- "make_external": _("Change to external group"),
- "make_posix": _("Change to POSIX group"),
- "normal": _("Normal"),
- "posix": _("POSIX"),
- "type": _("Group Type"),
- },
- "hbacrule": {
- "any_host": _("Any Host"),
- "any_service": _("Any Service"),
- "anyone": _("Anyone"),
- "host": _("Accessing"),
- "ipaenabledflag": _("Rule status"),
- "service": _("Via Service"),
- "specified_hosts": _("Specified Hosts and Groups"),
- "specified_services": _("Specified Services and Groups"),
- "specified_users": _("Specified Users and Groups"),
- "user": _("Who"),
- },
- "hbacsvc": {
- },
- "hbacsvcgroup": {
- "services": _("Services"),
- },
- "hbactest": {
- "access_denied": _("Access Denied"),
- "access_granted": _("Access Granted"),
- "include_disabled": _("Include Disabled"),
- "include_enabled": _("Include Enabled"),
- "label": _("HBAC Test"),
- "matched": _("Matched"),
- "missing_values": _("Missing values: "),
- "new_test": _("New Test"),
- "rules": _("Rules"),
- "run_test": _("Run Test"),
- "specify_external": _("Specify external ${entity}"),
- "unmatched": _("Unmatched"),
- },
- "host": {
- "certificate": _("Host Certificate"),
- "cn": _("Host Name"),
- "delete_key_unprovision": _("Delete Key, Unprovision"),
- "details": _("Host Settings"),
- "enrolled": _("Enrolled"),
- "enrollment": _("Enrollment"),
- "fqdn": _("Fully Qualified Host Name"),
- "generate_otp": _("Generate OTP"),
- "generated_otp": _("Generated OTP"),
- "keytab": _("Kerberos Key"),
- "keytab_missing": _("Kerberos Key Not Present"),
- "keytab_present": _("Kerberos Key Present, Host Provisioned"),
- "password": _("One-Time-Password"),
- "password_missing": _("One-Time-Password Not Present"),
- "password_present": _("One-Time-Password Present"),
- "password_reset_button": _("Reset OTP"),
- "password_reset_title": _("Reset One-Time-Password"),
- "password_set_button": _("Set OTP"),
- "password_set_success": _("OTP set"),
- "password_set_title": _("Set One-Time-Password"),
- "status": _("Status"),
- "unprovision": _("Unprovision"),
- "unprovision_confirmation": _("Are you sure you want to unprovision this host?"),
- "unprovision_title": _("Unprovisioning ${entity}"),
- "unprovisioned": _("Host unprovisioned"),
- },
- "hostgroup": {
- "identity": _("Host Group Settings"),
- },
- "idoverrideuser": {
- "anchor_label": _("User to override"),
- "anchor_tooltip": _("Enter trusted or IPA user login. Note: search doesn't list users from trusted domains."),
- "anchor_tooltip_ad": _("Enter trusted user login."),
- },
- "idoverridegroup": {
- "anchor_label": _("Group to override"),
- "anchor_tooltip": _("Enter trusted or IPA group name. Note: search doesn't list groups from trusted domains."),
- "anchor_tooltip_ad": _("Enter trusted group name."),
- },
- "idview": {
- "appliesto_tab": _("${primary_key} applies to:"),
- "appliedtohosts": _("Applied to hosts"),
- "appliedtohosts_title": _("Applied to hosts"),
- "apply_hostgroups": _("Apply to host groups"),
- "apply_hostgroups_title": _("Apply ID View ${primary_key} on hosts of ${entity}"),
- "apply_hosts": _("Apply to hosts"),
- "apply_hosts_title": _("Apply ID view ${primary_key} on ${entity}"),
- "ipaassignedidview": _("Assigned ID View"),
- "overrides_tab": _("${primary_key} overrides:"),
- "unapply_hostgroups": _("Un-apply from host groups"),
- "unapply_hostgroups_all_title": _("Un-apply ID Views from hosts of hostgroups"),
- "unapply_hostgroups_title": _("Un-apply ID View ${primary_key} from hosts of ${entity}"),
- "unapply_hosts": _("Un-apply"),
- "unapply_hosts_all": _("Un-apply from hosts"),
- "unapply_hosts_all_title": _("Un-apply ID Views from hosts"),
- "unapply_hosts_confirm": _("Are you sure you want to un-apply ID view from selected entries?"),
- "unapply_hosts_title": _("Un-apply ID View ${primary_key} from hosts"),
- },
- "krbtpolicy": {
- "identity": _("Kerberos Ticket Policy"),
- },
- "netgroup": {
- "any_host": _("Any Host"),
- "anyone": _("Anyone"),
- "external": _("External"),
- "host": _("Host"),
- "hostgroups": _("Host Groups"),
- "hosts": _("Hosts"),
- "identity": _("Netgroup Settings"),
- "specified_hosts": _("Specified Hosts and Groups"),
- "specified_users": _("Specified Users and Groups"),
- "user": _("User"),
- "usergroups": _("User Groups"),
- "users": _("Users"),
- },
- "otptoken": {
- "add_token": _("Add OTP Token"),
- "app_link": _("You can use <a href=\"${link}\" target=\"_blank\">FreeOTP<a/> as a software OTP token application."),
- "config_title": _("Configure your token"),
- "config_instructions": _("Configure your token by scanning the QR code below. Click on the QR code if you see this on the device you want to configure."),
- "details": _("OTP Token Settings"),
- "disable": _("Disable token"),
- "enable": _("Enable token"),
- "show_qr": _("Show QR code"),
- "show_uri": _("Show configuration uri"),
- "type_hotp": _("Counter-based (HOTP)"),
- "type_totp": _("Time-based (TOTP)"),
- },
- "permission": {
- "add_custom_attr": _("Add custom attribute"),
- "attribute": _("Attribute"),
- "filter": _("Filter"),
- "identity": _("Permission settings"),
- "managed": _("Attribute breakdown"),
- "target": _("Target"),
- },
- "privilege": {
- "identity": _("Privilege Settings"),
- },
- "pwpolicy": {
- "identity": _("Password Policy"),
- },
- "idrange": {
- "details": _("Range Settings"),
- "ipabaseid": _("Base ID"),
- "ipabaserid": _("Primary RID base"),
- "ipaidrangesize": _("Range size"),
- "ipanttrusteddomainsid": _("Domain SID"),
- "ipasecondarybaserid": _("Secondary RID base"),
- "type": _("Range type"),
- "type_ad": _("Active Directory domain"),
- "type_ad_posix": _("Active Directory domain with POSIX attributes"),
- "type_detect": _("Detect"),
- "type_local": _("Local domain"),
- "type_ipa": _("IPA trust"),
- "type_winsync": _("Active Directory winsync"),
- },
- "radiusproxy": {
- "details": _("RADIUS Proxy Server Settings"),
- },
- "realmdomains": {
- "identity": _("Realm Domains"),
- "check_dns": _("Check DNS"),
- "check_dns_confirmation": _("Do you also want to perform DNS check?"),
- "force_update": _("Force Update"),
- },
- "role": {
- "identity": _("Role Settings"),
- },
- "selfservice": {
- },
- "selinuxusermap": {
- "any_host": _("Any Host"),
- "anyone": _("Anyone"),
- "host": _("Host"),
- "specified_hosts": _("Specified Hosts and Groups"),
- "specified_users": _("Specified Users and Groups"),
- "user": _("User"),
- },
- "service": {
- "certificate": _("Service Certificate"),
- "delete_key_unprovision": _("Delete Key, Unprovision"),
- "details": _("Service Settings"),
- "host": _("Host Name"),
- "missing": _("Kerberos Key Not Present"),
- "provisioning": _("Provisioning"),
- "service": _("Service"),
- "status": _("Status"),
- "unprovision": _("Unprovision"),
- "unprovision_confirmation": _("Are you sure you want to unprovision this service?"),
- "unprovision_title": _("Unprovisioning ${entity}"),
- "unprovisioned": _("Service unprovisioned"),
- "valid": _("Kerberos Key Present, Service Provisioned"),
- },
- "sshkeystore": {
- "keys": _("SSH public keys"),
- "set_dialog_help": _("SSH public key:"),
- "set_dialog_title": _("Set SSH key"),
- "show_set_key": _("Show/Set key"),
- "status_mod_ns": _("Modified: key not set"),
- "status_mod_s": _("Modified"),
- "status_new_ns": _("New: key not set"),
- "status_new_s": _("New: key set"),
- },
- "stageuser": {
- "activate_confirm": _("Are you sure you want to activate selected users?"),
- "activate_one_confirm": _("Are you sure you want to activate ${object}?"),
- "activate_success": _("${count} user(s) activated"),
- "label": _("Stage users"),
- "preserved_label": _("Preserved users"),
- "undel_confirm": _("Are you sure you want to restore selected users?"),
- "undel_success": _("${count} user(s) restored"),
- "user_categories": _("User categories"),
- },
- "sudocmd": {
- "groups": _("Groups"),
- },
- "sudocmdgroup": {
- "commands": _("Commands"),
- },
- "sudorule": {
- "allow": _("Allow"),
- "any_command": _("Any Command"),
- "any_group": _("Any Group"),
- "any_host": _("Any Host"),
- "anyone": _("Anyone"),
- "command": _("Run Commands"),
- "deny": _("Deny"),
- "external": _("External"),
- "host": _("Access this host"),
- "ipaenabledflag": _("Rule status"),
- "option_added": _("Option added"),
- "option_removed": _("${count} option(s) removed"),
- "options": _("Options"),
- "runas": _("As Whom"),
- "specified_commands": _("Specified Commands and Groups"),
- "specified_groups": _("Specified Groups"),
- "specified_hosts": _("Specified Hosts and Groups"),
- "specified_users": _("Specified Users and Groups"),
- "user": _("Who"),
- },
- "topology": {
- "segment_details": _("Segment details"),
- "replication_config": _("Replication configuration"),
- "insufficient_domain_level" : _("Managed topology requires minimal domain level ${domainlevel}"),
- },
- "trust": {
- "account": _("Account"),
- "admin_account": _("Administrative account"),
- "blacklists": _("SID blacklists"),
- "details": _("Trust Settings"),
- "domain": _("Domain"),
- "establish_using": _("Establish using"),
- "fetch_domains": _("Fetch domains"),
- "ipantflatname": _("Domain NetBIOS name"),
- "ipanttrusteddomainsid": _("Domain Security Identifier"),
- "preshared_password": _("Pre-shared password"),
- "trustdirection": _("Trust direction"),
- "truststatus": _("Trust status"),
- "trusttype": _("Trust type"),
- },
- "trustconfig": {
- "options": _("Options"),
- },
- "user": {
- "account": _("Account Settings"),
- "account_status": _("Account Status"),
- "activeuser_label": _("Active users"),
- "contact": _("Contact Settings"),
- "delete_mode": _("Delete mode"),
- "employee": _("Employee Information"),
- "error_changing_status": _("Error changing account status"),
- "krbpasswordexpiration": _("Password expiration"),
- "mailing": _("Mailing Address"),
- "misc": _("Misc. Information"),
- "mode_delete": _("delete"),
- "mode_preserve": _("preserve"),
- "noprivate": _("No private group"),
- "status_confirmation": _("Are you sure you want to ${action} the user?<br/>The change will take effect immediately."),
- "status_link": _("Click to ${action}"),
- "unlock": _("Unlock"),
- "unlock_confirm": _("Are you sure you want to unlock user ${object}?"),
- },
- },
- "password": {
- "current_password": _("Current Password"),
- "current_password_required": _("Current password is required"),
- "expires_in": _("Your password expires in ${days} days."),
- "first_otp": _("First OTP"),
- "invalid_password": _("The password or username you entered is incorrect."),
- "new_password": _("New Password"),
- "new_password_required": _("New password is required"),
- "otp": _("OTP"),
- "otp_info": _("<i class=\"fa fa-info-circle\"></i> <strong>One-Time-Password(OTP):</strong> Generate new OTP code for each OTP field."),
- "otp_long": _("One-Time-Password"),
- "otp_sync_fail": _("Token synchronization failed"),
- "otp_sync_invalid": _("The username, password or token codes are not correct"),
- "otp_sync_success":_("Token was synchronized"),
- "password": _("Password"),
- "password_and_otp": _("Password or Password+One-Time-Password"),
- "password_change_complete": _("Password change complete"),
- "password_must_match": _("Passwords must match"),
- "reset_failure": _("Password reset was not successful."),
- "reset_password": _("Reset Password"),
- "reset_password_sentence": _("Reset your password."),
- "second_otp": _("Second OTP"),
- "token_id": _("Token ID"),
- "verify_password": _("Verify Password"),
- },
- "search": {
- "delete_confirm": _("Are you sure you want to delete selected entries?"),
- "deleted": _("${count} item(s) deleted"),
- "disable_confirm": _("Are you sure you want to disable selected entries?"),
- "disabled": _("${count} item(s) disabled"),
- "enable_confirm": _("Are you sure you want to enable selected entries?"),
- "enabled": _("${count} item(s) enabled"),
- "partial_delete": _("Some entries were not deleted"),
- "placeholder": _("Search"),
- "quick_links": _("Quick Links"),
- "select_all": _("Select All"),
- "truncated": _("Query returned more results than the configured size limit. Displaying the first ${counter} results."),
- "unselect_all": _("Unselect All"),
- },
- "status": {
- "disable": _("Disable"),
- "disabled": _("Disabled"),
- "enable": _("Enable"),
- "enabled": _("Enabled"),
- "label": _("Status"),
- "working": _("Working"),
- },
- "tabs": {
- "audit": _("Audit"),
- "authentication": _("Authentication"),
- "automember": _("Automember"),
- "automount": _("Automount"),
- "cert": _("Certificates"),
- "dns": _("DNS"),
- "hbac": _("Host Based Access Control"),
- "identity": _("Identity"),
- "ipaserver": _("IPA Server"),
- "network_services": _("Network Services"),
- "policy": _("Policy"),
- "role": _("Role Based Access Control"),
- "sudo": _("Sudo"),
- "topology": _("Topology"),
- "trust": _("Trusts"),
- },
- "true": _("True"),
- "widget": {
- "first": _("First"),
- "last": _("Last"),
- "next": _("Next"),
- "page": _("Page"),
- "prev": _("Prev"),
- "undo": _("Undo"),
- "undo_title": _("Undo this change."),
- "undo_all": _("Undo All"),
- "undo_all_title": _("Undo all changes in this field."),
- "validation": {
- "error": _("Text does not match field pattern"),
- "datetime": _("Must be an UTC date/time value (e.g., \"2014-01-20 17:58:01Z\")"),
- "decimal": _("Must be a decimal number"),
- "format": _("Format error"),
- "integer": _("Must be an integer"),
- "ip_address": _('Not a valid IP address'),
- "ip_v4_address": _('Not a valid IPv4 address'),
- "ip_v6_address": _('Not a valid IPv6 address'),
- "max_value": _("Maximum value is ${value}"),
- "min_value": _("Minimum value is ${value}"),
- "net_address": _("Not a valid network address (examples: 2001:db8::/64, 192.0.2.0/24)"),
- "parse": _("Parse error"),
- "port": _("'${port}' is not a valid port"),
- "required": _("Required field"),
- "unsupported": _("Unsupported value"),
- },
- },
- }
- has_output = (
- Output('texts', dict, doc=_('Dict of I18N messages')),
- )
- def execute(self, **options):
- return dict(texts=json_serialize(self.messages))
diff --git a/ipalib/plugins/krbtpolicy.py b/ipalib/plugins/krbtpolicy.py
deleted file mode 100644
index 7cf587661..000000000
--- a/ipalib/plugins/krbtpolicy.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# Authors:
-# 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 ipalib import api, errors, output, _
-from ipalib import Int, Str
-from . import baseldap
-from .baseldap import entry_to_dict, pkey_to_value
-from ipalib.plugable import Registry
-from ipapython.dn import DN
-
-__doc__ = _("""
-Kerberos ticket policy
-
-There is a single Kerberos ticket policy. This policy defines the
-maximum ticket lifetime and the maximum renewal age, the period during
-which the ticket is renewable.
-
-You can also create a per-user ticket policy by specifying the user login.
-
-For changes to the global policy to take effect, restarting the KDC service
-is required, which can be achieved using:
-
-service krb5kdc restart
-
-Changes to per-user policies take effect immediately for newly requested
-tickets (e.g. when the user next runs kinit).
-
-EXAMPLES:
-
- Display the current Kerberos ticket policy:
- ipa krbtpolicy-show
-
- Reset the policy to the default:
- ipa krbtpolicy-reset
-
- Modify the policy to 8 hours max life, 1-day max renewal:
- ipa krbtpolicy-mod --maxlife=28800 --maxrenew=86400
-
- Display effective Kerberos ticket policy for user 'admin':
- ipa krbtpolicy-show admin
-
- Reset per-user policy for user 'admin':
- ipa krbtpolicy-reset admin
-
- Modify per-user policy for user 'admin':
- ipa krbtpolicy-mod admin --maxlife=3600
-""")
-
-register = Registry()
-
-# FIXME: load this from a config file?
-_default_values = {
- 'krbmaxticketlife': 86400,
- 'krbmaxrenewableage': 604800,
-}
-
-
-@register()
-class krbtpolicy(baseldap.LDAPObject):
- """
- Kerberos Ticket Policy object
- """
- container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos'))
- object_name = _('kerberos ticket policy settings')
- default_attributes = ['krbmaxticketlife', 'krbmaxrenewableage']
- limit_object_classes = ['krbticketpolicyaux']
- # permission_filter_objectclasses is deliberately missing,
- # so it is not possible to create a permission of `--type krbtpolicy`.
- # This is because we need two permissions to cover both global and per-user
- # policies.
- managed_permissions = {
- 'System: Read Default Kerberos Ticket Policy': {
- 'non_object': True,
- 'replaces_global_anonymous_aci': True,
- 'ipapermtargetfilter': ['(objectclass=krbticketpolicyaux)'],
- 'ipapermlocation': DN(container_dn, api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'krbdefaultencsalttypes', 'krbmaxrenewableage',
- 'krbmaxticketlife', 'krbsupportedencsalttypes',
- 'objectclass',
- },
- 'default_privileges': {
- 'Kerberos Ticket Policy Readers',
- },
- },
- 'System: Read User Kerberos Ticket Policy': {
- 'non_object': True,
- 'replaces_global_anonymous_aci': True,
- 'ipapermlocation': DN(api.env.container_user, api.env.basedn),
- 'ipapermtargetfilter': ['(objectclass=krbticketpolicyaux)'],
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'krbmaxrenewableage', 'krbmaxticketlife',
- },
- 'default_privileges': {
- 'Kerberos Ticket Policy Readers',
- },
- },
- }
-
- label = _('Kerberos Ticket Policy')
- label_singular = _('Kerberos Ticket Policy')
-
- takes_params = (
- Str('uid?',
- cli_name='user',
- label=_('User name'),
- doc=_('Manage ticket policy for specific user'),
- primary_key=True,
- ),
- Int('krbmaxticketlife?',
- cli_name='maxlife',
- label=_('Max life'),
- doc=_('Maximum ticket life (seconds)'),
- minvalue=1,
- ),
- Int('krbmaxrenewableage?',
- cli_name='maxrenew',
- label=_('Max renew'),
- doc=_('Maximum renewable age (seconds)'),
- minvalue=1,
- ),
- )
-
- def get_dn(self, *keys, **kwargs):
- if keys[-1] is not None:
- return self.api.Object.user.get_dn(*keys, **kwargs)
- return DN(self.container_dn, api.env.basedn)
-
-
-@register()
-class krbtpolicy_mod(baseldap.LDAPUpdate):
- __doc__ = _('Modify Kerberos ticket policy.')
-
- def execute(self, uid=None, **options):
- return super(krbtpolicy_mod, self).execute(uid, **options)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # disable all flag
- # ticket policies are attached to objects with unrelated attributes
- if options.get('all'):
- options['all'] = False
- return dn
-
-
-@register()
-class krbtpolicy_show(baseldap.LDAPRetrieve):
- __doc__ = _('Display the current Kerberos ticket policy.')
-
- def execute(self, uid=None, **options):
- return super(krbtpolicy_show, self).execute(uid, **options)
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # disable all flag
- # ticket policies are attached to objects with unrelated attributes
- if options.get('all'):
- options['all'] = False
- return dn
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- default_entry = None
- rights = None
- for attrname in self.obj.default_attributes:
- if attrname not in entry:
- if keys[-1] is not None:
- # User entry doesn't override the attribute.
- # Check if this is caused by insufficient read rights
- if rights is None:
- rights = baseldap.get_effective_rights(
- ldap, dn, self.obj.default_attributes)
- if 'r' not in rights.get(attrname.lower(), ''):
- raise errors.ACIError(
- info=_('Ticket policy for %s could not be read') %
- keys[-1])
- # Fallback to the default
- if default_entry is None:
- try:
- default_dn = self.obj.get_dn(None)
- default_entry = ldap.get_entry(default_dn)
- except errors.NotFound:
- default_entry = {}
- if attrname in default_entry:
- entry[attrname] = default_entry[attrname]
- if attrname not in entry:
- raise errors.ACIError(
- info=_('Default ticket policy could not be read'))
- return dn
-
-
-@register()
-class krbtpolicy_reset(baseldap.LDAPQuery):
- __doc__ = _('Reset Kerberos ticket policy to the default values.')
-
- has_output = output.standard_entry
-
- def execute(self, uid=None, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(uid, **options)
-
- def_values = {}
- # if reseting policy for a user - just his values
- if uid is not None:
- for a in self.obj.default_attributes:
- def_values[a] = None
- # if reseting global policy - set values to default
- else:
- def_values = _default_values
-
- entry = ldap.get_entry(dn, def_values.keys())
- entry.update(def_values)
- try:
- ldap.update_entry(entry)
- except errors.EmptyModlist:
- pass
-
- if uid is not None:
- # policy for user was deleted, retrieve global policy
- dn = self.obj.get_dn(None)
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- return dict(result=entry_attrs, value=pkey_to_value(uid, options))
diff --git a/ipalib/plugins/migration.py b/ipalib/plugins/migration.py
deleted file mode 100644
index 7f634a7cc..000000000
--- a/ipalib/plugins/migration.py
+++ /dev/null
@@ -1,920 +0,0 @@
-# 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 re
-from ldap import MOD_ADD
-from ldap import SCOPE_BASE, SCOPE_ONELEVEL, SCOPE_SUBTREE
-
-import six
-
-from ipalib import api, errors, output
-from ipalib import Command, Password, Str, Flag, StrEnum, DNParam, Bool
-from ipalib.cli import to_cli
-from ipalib.plugable import Registry
-from .user import NO_UPG_MAGIC
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- from ipaserver.plugins.ldap2 import ldap2
- except Exception as e:
- raise e
-from ipalib import _
-from ipapython.dn import DN
-from ipapython.ipautil import write_tmp_file
-import datetime
-from ipaplatform.paths import paths
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Migration to IPA
-
-Migrate users and groups from an LDAP server to IPA.
-
-This performs an LDAP query against the remote server searching for
-users and groups in a container. In order to migrate passwords you need
-to bind as a user that can read the userPassword attribute on the remote
-server. This is generally restricted to high-level admins such as
-cn=Directory Manager in 389-ds (this is the default bind user).
-
-The default user container is ou=People.
-
-The default group container is ou=Groups.
-
-Users and groups that already exist on the IPA server are skipped.
-
-Two LDAP schemas define how group members are stored: RFC2307 and
-RFC2307bis. RFC2307bis uses member and uniquemember to specify group
-members, RFC2307 uses memberUid. The default schema is RFC2307bis.
-
-The schema compat feature allows IPA to reformat data for systems that
-do not support RFC2307bis. It is recommended that this feature is disabled
-during migration to reduce system overhead. It can be re-enabled after
-migration. To migrate with it enabled use the "--with-compat" option.
-
-Migrated users do not have Kerberos credentials, they have only their
-LDAP password. To complete the migration process, users need to go
-to http://ipa.example.com/ipa/migration and authenticate using their
-LDAP password in order to generate their Kerberos credentials.
-
-Migration is disabled by default. Use the command ipa config-mod to
-enable it:
-
- ipa config-mod --enable-migration=TRUE
-
-If a base DN is not provided with --basedn then IPA will use either
-the value of defaultNamingContext if it is set or the first value
-in namingContexts set in the root of the remote LDAP server.
-
-Users are added as members to the default user group. This can be a
-time-intensive task so during migration this is done in a batch
-mode for every 100 users. As a result there will be a window in which
-users will be added to IPA but will not be members of the default
-user group.
-
-EXAMPLES:
-
- The simplest migration, accepting all defaults:
- ipa migrate-ds ldap://ds.example.com:389
-
- Specify the user and group container. This can be used to migrate user
- and group data from an IPA v1 server:
- ipa migrate-ds --user-container='cn=users,cn=accounts' \\
- --group-container='cn=groups,cn=accounts' \\
- ldap://ds.example.com:389
-
- Since IPA v2 server already contain predefined groups that may collide with
- groups in migrated (IPA v1) server (for example admins, ipausers), users
- having colliding group as their primary group may happen to belong to
- an unknown group on new IPA v2 server.
- Use --group-overwrite-gid option to overwrite GID of already existing groups
- to prevent this issue:
- ipa migrate-ds --group-overwrite-gid \\
- --user-container='cn=users,cn=accounts' \\
- --group-container='cn=groups,cn=accounts' \\
- ldap://ds.example.com:389
-
- Migrated users or groups may have object class and accompanied attributes
- unknown to the IPA v2 server. These object classes and attributes may be
- left out of the migration process:
- ipa migrate-ds --user-container='cn=users,cn=accounts' \\
- --group-container='cn=groups,cn=accounts' \\
- --user-ignore-objectclass=radiusprofile \\
- --user-ignore-attribute=radiusgroupname \\
- ldap://ds.example.com:389
-
-LOGGING
-
-Migration will log warnings and errors to the Apache error log. This
-file should be evaluated post-migration to correct or investigate any
-issues that were discovered.
-
-For every 100 users migrated an info-level message will be displayed to
-give the current progress and duration to make it possible to track
-the progress of migration.
-
-If the log level is debug, either by setting debug = True in
-/etc/ipa/default.conf or /etc/ipa/server.conf, then an entry will be printed
-for each user added plus a summary when the default user group is
-updated.
-""")
-
-register = Registry()
-
-# USER MIGRATION CALLBACKS AND VARS
-
-_krb_err_msg = _('Kerberos principal %s already exists. Use \'ipa user-mod\' to set it manually.')
-_krb_failed_msg = _('Unable to determine if Kerberos principal %s already exists. Use \'ipa user-mod\' to set it manually.')
-_grp_err_msg = _('Failed to add user to the default group. Use \'ipa group-add-member\' to add manually.')
-_ref_err_msg = _('Migration of LDAP search reference is not supported.')
-_dn_err_msg = _('Malformed DN')
-
-_supported_schemas = (u'RFC2307bis', u'RFC2307')
-
-# search scopes for users and groups when migrating
-_supported_scopes = {u'base': SCOPE_BASE, u'onelevel': SCOPE_ONELEVEL, u'subtree': SCOPE_SUBTREE}
-_default_scope = u'onelevel'
-
-
-def _pre_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs):
- assert isinstance(dn, DN)
- attr_blacklist = ['krbprincipalkey','memberofindirect','memberindirect']
- attr_blacklist.extend(kwargs.get('attr_blacklist', []))
- ds_ldap = ctx['ds_ldap']
- has_upg = ctx['has_upg']
- search_bases = kwargs.get('search_bases', None)
- valid_gids = kwargs['valid_gids']
- invalid_gids = kwargs['invalid_gids']
-
- if 'gidnumber' not in entry_attrs:
- raise errors.NotFound(reason=_('%(user)s is not a POSIX user') % dict(user=pkey))
- else:
- # See if the gidNumber at least points to a valid group on the remote
- # server.
- if entry_attrs['gidnumber'][0] in invalid_gids:
- api.log.warning('GID number %s of migrated user %s does not point to a known group.' \
- % (entry_attrs['gidnumber'][0], pkey))
- elif entry_attrs['gidnumber'][0] not in valid_gids:
- try:
- remote_entry = ds_ldap.find_entry_by_attr(
- 'gidnumber', entry_attrs['gidnumber'][0], 'posixgroup',
- [''], search_bases['group']
- )
- valid_gids.add(entry_attrs['gidnumber'][0])
- except errors.NotFound:
- api.log.warning('GID number %s of migrated user %s does not point to a known group.' \
- % (entry_attrs['gidnumber'][0], pkey))
- invalid_gids.add(entry_attrs['gidnumber'][0])
- except errors.SingleMatchExpected as e:
- # GID number matched more groups, this should not happen
- api.log.warning('GID number %s of migrated user %s should match 1 group, but it matched %d groups' \
- % (entry_attrs['gidnumber'][0], pkey, e.found))
- except errors.LimitsExceeded as e:
- api.log.warning('Search limit exceeded searching for GID %s' % entry_attrs['gidnumber'][0])
-
- # We don't want to create a UPG so set the magic value in description
- # to let the DS plugin know.
- entry_attrs.setdefault('description', [])
- entry_attrs['description'].append(NO_UPG_MAGIC)
-
- # fill in required attributes by IPA
- entry_attrs['ipauniqueid'] = 'autogenerate'
- if 'homedirectory' not in entry_attrs:
- homes_root = config.get('ipahomesrootdir', (paths.HOME_DIR, ))[0]
- home_dir = '%s/%s' % (homes_root, pkey)
- home_dir = home_dir.replace('//', '/').rstrip('/')
- entry_attrs['homedirectory'] = home_dir
-
- if 'loginshell' not in entry_attrs:
- default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
- entry_attrs.setdefault('loginshell', default_shell)
-
- # do not migrate all attributes
- for attr in attr_blacklist:
- entry_attrs.pop(attr, None)
-
- # do not migrate all object classes
- if 'objectclass' in entry_attrs:
- for object_class in kwargs.get('oc_blacklist', []):
- try:
- entry_attrs['objectclass'].remove(object_class)
- except ValueError: # object class not present
- pass
-
- # generate a principal name and check if it isn't already taken
- principal = u'%s@%s' % (pkey, api.env.realm)
- try:
- ldap.find_entry_by_attr(
- 'krbprincipalname', principal, 'krbprincipalaux', [''],
- DN(api.env.container_user, api.env.basedn)
- )
- except errors.NotFound:
- entry_attrs['krbprincipalname'] = principal
- except errors.LimitsExceeded:
- failed[pkey] = unicode(_krb_failed_msg % principal)
- else:
- failed[pkey] = unicode(_krb_err_msg % principal)
-
- # Fix any attributes with DN syntax that point to entries in the old
- # tree
-
- for attr in entry_attrs.keys():
- if ldap.has_dn_syntax(attr):
- for ind, value in enumerate(entry_attrs[attr]):
- if not isinstance(value, DN):
- # value is not DN instance, the automatic encoding may have
- # failed due to missing schema or the remote attribute type OID was
- # not detected as DN type. Try to work this around
- api.log.debug('%s: value %s of type %s in attribute %s is not a DN'
- ', convert it', pkey, value, type(value), attr)
- try:
- value = DN(value)
- except ValueError as e:
- api.log.warning('%s: skipping normalization of value %s of type %s '
- 'in attribute %s which could not be converted to DN: %s',
- pkey, value, type(value), attr, e)
- continue
- try:
- remote_entry = ds_ldap.get_entry(value, [api.Object.user.primary_key.name, api.Object.group.primary_key.name])
- except errors.NotFound:
- api.log.warning('%s: attribute %s refers to non-existent entry %s' % (pkey, attr, value))
- continue
- if value.endswith(search_bases['user']):
- primary_key = api.Object.user.primary_key.name
- container = api.env.container_user
- elif value.endswith(search_bases['group']):
- primary_key = api.Object.group.primary_key.name
- container = api.env.container_group
- else:
- api.log.warning('%s: value %s in attribute %s does not belong into any known container' % (pkey, value, attr))
- continue
-
- if not remote_entry.get(primary_key):
- api.log.warning('%s: there is no primary key %s to migrate for %s' % (pkey, primary_key, attr))
- continue
-
- api.log.debug('converting DN value %s for %s in %s' % (value, attr, dn))
- rdnval = remote_entry[primary_key][0].lower()
- entry_attrs[attr][ind] = DN((primary_key, rdnval), container, api.env.basedn)
-
- return dn
-
-
-def _post_migrate_user(ldap, pkey, dn, entry_attrs, failed, config, ctx):
- assert isinstance(dn, DN)
-
- if 'def_group_dn' in ctx:
- _update_default_group(ldap, ctx, False)
-
- if 'description' in entry_attrs and NO_UPG_MAGIC in entry_attrs['description']:
- entry_attrs['description'].remove(NO_UPG_MAGIC)
- try:
- update_attrs = ldap.get_entry(dn, ['description'])
- update_attrs['description'] = entry_attrs['description']
- ldap.update_entry(update_attrs)
- except (errors.EmptyModlist, errors.NotFound):
- pass
-
-def _update_default_group(ldap, ctx, force):
- migrate_cnt = ctx['migrate_cnt']
- group_dn = ctx['def_group_dn']
-
- # Purposely let this fire when migrate_cnt == 0 so on re-running migration
- # it can catch any users migrated but not added to the default group.
- if force or migrate_cnt % 100 == 0:
- s = datetime.datetime.now()
- searchfilter = "(&(objectclass=posixAccount)(!(memberof=%s)))" % group_dn
- try:
- (result, truncated) = ldap.find_entries(searchfilter,
- [''], DN(api.env.container_user, api.env.basedn),
- scope=ldap.SCOPE_SUBTREE, time_limit=-1, size_limit=-1)
- except errors.NotFound:
- api.log.debug('All users have default group set')
- return
-
- member_dns = [m.dn for m in result]
- modlist = [(MOD_ADD, 'member', ldap.encode(member_dns))]
- try:
- with ldap.error_handler():
- ldap.conn.modify_s(str(group_dn), modlist)
- except errors.DatabaseError as e:
- api.log.error('Adding new members to default group failed: %s \n'
- 'members: %s', e, ','.join(member_dns))
-
- e = datetime.datetime.now()
- d = e - s
- mode = " (forced)" if force else ""
- api.log.info('Adding %d users to group%s duration %s',
- len(member_dns), mode, d)
-
-# GROUP MIGRATION CALLBACKS AND VARS
-
-def _pre_migrate_group(ldap, pkey, dn, entry_attrs, failed, config, ctx, **kwargs):
-
- def convert_members_rfc2307bis(member_attr, search_bases, overwrite=False):
- """
- Convert DNs in member attributes to work in IPA.
- """
- new_members = []
- entry_attrs.setdefault(member_attr, [])
- for m in entry_attrs[member_attr]:
- try:
- m = DN(m)
- except ValueError as e:
- # This should be impossible unless the remote server
- # doesn't enforce syntax checking.
- api.log.error('Malformed DN %s: %s' % (m, e))
- continue
- try:
- rdnval = m[0].value
- except IndexError:
- api.log.error('Malformed DN %s has no RDN?' % m)
- continue
-
- if m.endswith(search_bases['user']):
- api.log.debug('migrating %s user %s', member_attr, m)
- m = DN((api.Object.user.primary_key.name, rdnval),
- api.env.container_user, api.env.basedn)
- elif m.endswith(search_bases['group']):
- api.log.debug('migrating %s group %s', member_attr, m)
- m = DN((api.Object.group.primary_key.name, rdnval),
- api.env.container_group, api.env.basedn)
- else:
- api.log.error('entry %s does not belong into any known container' % m)
- continue
-
- new_members.append(m)
-
- del entry_attrs[member_attr]
- if overwrite:
- entry_attrs['member'] = []
- entry_attrs['member'] += new_members
-
- def convert_members_rfc2307(member_attr):
- """
- Convert usernames in member attributes to work in IPA.
- """
- new_members = []
- entry_attrs.setdefault(member_attr, [])
- for m in entry_attrs[member_attr]:
- memberdn = DN((api.Object.user.primary_key.name, m),
- api.env.container_user, api.env.basedn)
- new_members.append(memberdn)
- entry_attrs['member'] = new_members
-
- assert isinstance(dn, DN)
- attr_blacklist = ['memberofindirect','memberindirect']
- attr_blacklist.extend(kwargs.get('attr_blacklist', []))
-
- schema = kwargs.get('schema', None)
- entry_attrs['ipauniqueid'] = 'autogenerate'
- if schema == 'RFC2307bis':
- search_bases = kwargs.get('search_bases', None)
- if not search_bases:
- raise ValueError('Search bases not specified')
-
- convert_members_rfc2307bis('member', search_bases, overwrite=True)
- convert_members_rfc2307bis('uniquemember', search_bases)
- elif schema == 'RFC2307':
- convert_members_rfc2307('memberuid')
- else:
- raise ValueError('Schema %s not supported' % schema)
-
- # do not migrate all attributes
- for attr in attr_blacklist:
- entry_attrs.pop(attr, None)
-
- # do not migrate all object classes
- if 'objectclass' in entry_attrs:
- for object_class in kwargs.get('oc_blacklist', []):
- try:
- entry_attrs['objectclass'].remove(object_class)
- except ValueError: # object class not present
- pass
-
- return dn
-
-
-def _group_exc_callback(ldap, dn, entry_attrs, exc, options):
- assert isinstance(dn, DN)
- if isinstance(exc, errors.DuplicateEntry):
- if options.get('groupoverwritegid', False) and \
- entry_attrs.get('gidnumber') is not None:
- try:
- new_entry_attrs = ldap.get_entry(dn, ['gidnumber'])
- new_entry_attrs['gidnumber'] = entry_attrs['gidnumber']
- ldap.update_entry(new_entry_attrs)
- except errors.EmptyModlist:
- # no change to the GID
- pass
- # mark as success
- return
- elif not options.get('groupoverwritegid', False) and \
- entry_attrs.get('gidnumber') is not None:
- msg = unicode(exc)
- # add information about possibility to overwrite GID
- msg = msg + unicode(_('. Check GID of the existing group. ' \
- 'Use --group-overwrite-gid option to overwrite the GID'))
- raise errors.DuplicateEntry(message=msg)
-
- raise exc
-
-# DS MIGRATION PLUGIN
-
-def construct_filter(template, oc_list):
- oc_subfilter = ''.join([ '(objectclass=%s)' % oc for oc in oc_list])
- return template % oc_subfilter
-
-def validate_ldapuri(ugettext, ldapuri):
- m = re.match('^ldaps?://[-\w\.]+(:\d+)?$', ldapuri)
- if not m:
- err_msg = _('Invalid LDAP URI.')
- raise errors.ValidationError(name='ldap_uri', error=err_msg)
-
-
-@register()
-class migrate_ds(Command):
- __doc__ = _('Migrate users and groups from DS to IPA.')
-
- migrate_objects = {
- # OBJECT_NAME: (search_filter, pre_callback, post_callback)
- #
- # OBJECT_NAME - is the name of an LDAPObject subclass
- # search_filter - is the filter to retrieve objects from DS
- # pre_callback - is called for each object just after it was
- # retrieved from DS and before being added to IPA
- # post_callback - is called for each object after it was added to IPA
- # exc_callback - is called when adding entry to IPA raises an exception
- #
- # {pre, post}_callback parameters:
- # ldap - ldap2 instance connected to IPA
- # pkey - primary key value of the object (uid for users, etc.)
- # dn - dn of the object as it (will be/is) stored in IPA
- # entry_attrs - attributes of the object
- # failed - a list of so-far failed objects
- # config - IPA config entry attributes
- # ctx - object context, used to pass data between callbacks
- #
- # If pre_callback return value evaluates to False, migration
- # of the current object is aborted.
- 'user': {
- 'filter_template' : '(&(|%s)(uid=*))',
- 'oc_option' : 'userobjectclass',
- 'oc_blacklist_option' : 'userignoreobjectclass',
- 'attr_blacklist_option' : 'userignoreattribute',
- 'pre_callback' : _pre_migrate_user,
- 'post_callback' : _post_migrate_user,
- 'exc_callback' : None
- },
- 'group': {
- 'filter_template' : '(&(|%s)(cn=*))',
- 'oc_option' : 'groupobjectclass',
- 'oc_blacklist_option' : 'groupignoreobjectclass',
- 'attr_blacklist_option' : 'groupignoreattribute',
- 'pre_callback' : _pre_migrate_group,
- 'post_callback' : None,
- 'exc_callback' : _group_exc_callback,
- },
- }
- migrate_order = ('user', 'group')
-
- takes_args = (
- Str('ldapuri', validate_ldapuri,
- cli_name='ldap_uri',
- label=_('LDAP URI'),
- doc=_('LDAP URI of DS server to migrate from'),
- ),
- Password('bindpw',
- cli_name='password',
- label=_('Password'),
- confirm=False,
- doc=_('bind password'),
- ),
- )
-
- takes_options = (
- DNParam('binddn?',
- cli_name='bind_dn',
- label=_('Bind DN'),
- default=DN(('cn', 'directory manager')),
- autofill=True,
- ),
- DNParam('usercontainer',
- cli_name='user_container',
- label=_('User container'),
- doc=_('DN of container for users in DS relative to base DN'),
- default=DN(('ou', 'people')),
- autofill=True,
- ),
- DNParam('groupcontainer',
- cli_name='group_container',
- label=_('Group container'),
- doc=_('DN of container for groups in DS relative to base DN'),
- default=DN(('ou', 'groups')),
- autofill=True,
- ),
- Str('userobjectclass+',
- cli_name='user_objectclass',
- label=_('User object class'),
- doc=_('Objectclasses used to search for user entries in DS'),
- default=(u'person',),
- autofill=True,
- ),
- Str('groupobjectclass+',
- cli_name='group_objectclass',
- label=_('Group object class'),
- doc=_('Objectclasses used to search for group entries in DS'),
- default=(u'groupOfUniqueNames', u'groupOfNames'),
- autofill=True,
- ),
- Str('userignoreobjectclass*',
- cli_name='user_ignore_objectclass',
- label=_('Ignore user object class'),
- doc=_('Objectclasses to be ignored for user entries in DS'),
- default=tuple(),
- autofill=True,
- ),
- Str('userignoreattribute*',
- cli_name='user_ignore_attribute',
- label=_('Ignore user attribute'),
- doc=_('Attributes to be ignored for user entries in DS'),
- default=tuple(),
- autofill=True,
- ),
- Str('groupignoreobjectclass*',
- cli_name='group_ignore_objectclass',
- label=_('Ignore group object class'),
- doc=_('Objectclasses to be ignored for group entries in DS'),
- default=tuple(),
- autofill=True,
- ),
- Str('groupignoreattribute*',
- cli_name='group_ignore_attribute',
- label=_('Ignore group attribute'),
- doc=_('Attributes to be ignored for group entries in DS'),
- default=tuple(),
- autofill=True,
- ),
- Flag('groupoverwritegid',
- cli_name='group_overwrite_gid',
- label=_('Overwrite GID'),
- doc=_('When migrating a group already existing in IPA domain overwrite the '\
- 'group GID and report as success'),
- ),
- StrEnum('schema?',
- cli_name='schema',
- label=_('LDAP schema'),
- doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'),
- values=_supported_schemas,
- default=_supported_schemas[0],
- autofill=True,
- ),
- Flag('continue?',
- label=_('Continue'),
- doc=_('Continuous operation mode. Errors are reported but the process continues'),
- default=False,
- ),
- DNParam('basedn?',
- cli_name='base_dn',
- label=_('Base DN'),
- doc=_('Base DN on remote LDAP server'),
- ),
- Flag('compat?',
- cli_name='with_compat',
- label=_('Ignore compat plugin'),
- doc=_('Allows migration despite the usage of compat plugin'),
- default=False,
- ),
- Str('cacertfile?',
- cli_name='ca_cert_file',
- label=_('CA certificate'),
- doc=_('Load CA certificate of LDAP server from FILE'),
- default=None,
- noextrawhitespace=False,
- ),
- Bool('use_def_group?',
- cli_name='use_default_group',
- label=_('Add to default group'),
- doc=_('Add migrated users without a group to a default group '
- '(default: true)'),
- default=True,
- autofill=True,
- ),
- StrEnum('scope',
- cli_name='scope',
- label=_('Search scope'),
- doc=_('LDAP search scope for users and groups: base, onelevel, or '
- 'subtree. Defaults to onelevel'),
- values=tuple(_supported_scopes.keys()),
- default=_default_scope,
- autofill=True,
- ),
- )
-
- has_output = (
- output.Output('result',
- type=dict,
- doc=_('Lists of objects migrated; categorized by type.'),
- ),
- output.Output('failed',
- type=dict,
- doc=_('Lists of objects that could not be migrated; categorized by type.'),
- ),
- output.Output('enabled',
- type=bool,
- doc=_('False if migration mode was disabled.'),
- ),
- output.Output('compat',
- type=bool,
- doc=_('False if migration fails because the compatibility plug-in is enabled.'),
- ),
- )
-
- exclude_doc = _('%s to exclude from migration')
-
- truncated_err_msg = _('''\
-search results for objects to be migrated
-have been truncated by the server;
-migration process might be incomplete\n''')
-
- def get_options(self):
- """
- Call get_options of the baseclass and add "exclude" options
- for each type of object being migrated.
- """
- for option in super(migrate_ds, self).get_options():
- yield option
- for ldap_obj_name in self.migrate_objects:
- ldap_obj = self.api.Object[ldap_obj_name]
- name = 'exclude_%ss' % to_cli(ldap_obj_name)
- doc = self.exclude_doc % ldap_obj.object_name_plural
- yield Str(
- '%s*' % name, cli_name=name, doc=doc, default=tuple(),
- autofill=True
- )
-
- def normalize_options(self, options):
- """
- Convert all "exclude" option values to lower-case.
-
- Also, empty List parameters are converted to None, but the migration
- plugin doesn't like that - convert back to empty lists.
- """
- names = ['userobjectclass', 'groupobjectclass',
- 'userignoreobjectclass', 'userignoreattribute',
- 'groupignoreobjectclass', 'groupignoreattribute']
- names.extend('exclude_%ss' % to_cli(n) for n in self.migrate_objects)
- for name in names:
- if options[name]:
- options[name] = tuple(
- v.lower() for v in options[name]
- )
- else:
- options[name] = tuple()
-
- def _get_search_bases(self, options, ds_base_dn, migrate_order):
- search_bases = dict()
- for ldap_obj_name in migrate_order:
- container = options.get('%scontainer' % to_cli(ldap_obj_name))
- if container:
- # Don't append base dn if user already appended it in the container dn
- if container.endswith(ds_base_dn):
- search_base = container
- else:
- search_base = DN(container, ds_base_dn)
- else:
- search_base = ds_base_dn
- search_bases[ldap_obj_name] = search_base
- return search_bases
-
- def migrate(self, ldap, config, ds_ldap, ds_base_dn, options):
- """
- Migrate objects from DS to LDAP.
- """
- assert isinstance(ds_base_dn, DN)
- migrated = {} # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
- failed = {} # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
- search_bases = self._get_search_bases(options, ds_base_dn, self.migrate_order)
- migration_start = datetime.datetime.now()
-
- scope = _supported_scopes[options.get('scope')]
-
- for ldap_obj_name in self.migrate_order:
- ldap_obj = self.api.Object[ldap_obj_name]
-
- template = self.migrate_objects[ldap_obj_name]['filter_template']
- oc_list = options[to_cli(self.migrate_objects[ldap_obj_name]['oc_option'])]
- search_filter = construct_filter(template, oc_list)
-
- exclude = options['exclude_%ss' % to_cli(ldap_obj_name)]
- context = dict(ds_ldap = ds_ldap)
-
- migrated[ldap_obj_name] = []
- failed[ldap_obj_name] = {}
-
- try:
- entries, truncated = ds_ldap.find_entries(
- search_filter, ['*'], search_bases[ldap_obj_name],
- scope,
- time_limit=0, size_limit=-1,
- search_refs=True # migrated DS may contain search references
- )
- except errors.NotFound:
- if not options.get('continue',False):
- raise errors.NotFound(
- reason=_('%(container)s LDAP search did not return any result '
- '(search base: %(search_base)s, '
- 'objectclass: %(objectclass)s)')
- % {'container': ldap_obj_name,
- 'search_base': search_bases[ldap_obj_name],
- 'objectclass': ', '.join(oc_list)}
- )
- else:
- truncated = False
- entries = []
- if truncated:
- self.log.error(
- '%s: %s' % (
- ldap_obj.name, self.truncated_err_msg
- )
- )
-
- blacklists = {}
- for blacklist in ('oc_blacklist', 'attr_blacklist'):
- blacklist_option = self.migrate_objects[ldap_obj_name][blacklist+'_option']
- if blacklist_option is not None:
- blacklists[blacklist] = options.get(blacklist_option, tuple())
- else:
- blacklists[blacklist] = tuple()
-
- # get default primary group for new users
- if 'def_group_dn' not in context and options.get('use_def_group'):
- def_group = config.get('ipadefaultprimarygroup')
- context['def_group_dn'] = api.Object.group.get_dn(def_group)
- try:
- ldap.get_entry(context['def_group_dn'], ['gidnumber', 'cn'])
- except errors.NotFound:
- error_msg = _('Default group for new users not found')
- raise errors.NotFound(reason=error_msg)
-
- context['has_upg'] = ldap.has_upg()
-
- valid_gids = set()
- invalid_gids = set()
- migrate_cnt = 0
- context['migrate_cnt'] = 0
- for entry_attrs in entries:
- context['migrate_cnt'] = migrate_cnt
- s = datetime.datetime.now()
-
- ava = entry_attrs.dn[0][0]
- if ava.attr == ldap_obj.primary_key.name:
- # In case if pkey attribute is in the migrated object DN
- # and the original LDAP is multivalued, make sure that
- # we pick the correct value (the unique one stored in DN)
- pkey = ava.value.lower()
- else:
- pkey = entry_attrs[ldap_obj.primary_key.name][0].lower()
-
- if pkey in exclude:
- continue
-
- entry_attrs.dn = ldap_obj.get_dn(pkey)
- entry_attrs['objectclass'] = list(
- set(
- config.get(
- ldap_obj.object_class_config, ldap_obj.object_class
- ) + [o.lower() for o in entry_attrs['objectclass']]
- )
- )
- entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[ldap_obj.primary_key.name][0].lower()
-
- callback = self.migrate_objects[ldap_obj_name]['pre_callback']
- if callable(callback):
- try:
- entry_attrs.dn = callback(
- ldap, pkey, entry_attrs.dn, entry_attrs,
- failed[ldap_obj_name], config, context,
- schema=options['schema'],
- search_bases=search_bases,
- valid_gids=valid_gids,
- invalid_gids=invalid_gids,
- **blacklists
- )
- if not entry_attrs.dn:
- continue
- except errors.NotFound as e:
- failed[ldap_obj_name][pkey] = unicode(e.reason)
- continue
-
- try:
- ldap.add_entry(entry_attrs)
- except errors.ExecutionError as e:
- callback = self.migrate_objects[ldap_obj_name]['exc_callback']
- if callable(callback):
- try:
- callback(
- ldap, entry_attrs.dn, entry_attrs, e, options)
- except errors.ExecutionError as e:
- failed[ldap_obj_name][pkey] = unicode(e)
- continue
- else:
- failed[ldap_obj_name][pkey] = unicode(e)
- continue
-
- migrated[ldap_obj_name].append(pkey)
-
- callback = self.migrate_objects[ldap_obj_name]['post_callback']
- if callable(callback):
- callback(
- ldap, pkey, entry_attrs.dn, entry_attrs,
- failed[ldap_obj_name], config, context)
- e = datetime.datetime.now()
- d = e - s
- total_dur = e - migration_start
- migrate_cnt += 1
- if migrate_cnt > 0 and migrate_cnt % 100 == 0:
- api.log.info("%d %ss migrated. %s elapsed." % (migrate_cnt, ldap_obj_name, total_dur))
- api.log.debug("%d %ss migrated, duration: %s (total %s)" % (migrate_cnt, ldap_obj_name, d, total_dur))
-
- if 'def_group_dn' in context:
- _update_default_group(ldap, context, True)
-
- return (migrated, failed)
-
- def execute(self, ldapuri, bindpw, **options):
- ldap = self.api.Backend.ldap2
- self.normalize_options(options)
- config = ldap.get_ipa_config()
-
- ds_base_dn = options.get('basedn')
- if ds_base_dn is not None:
- assert isinstance(ds_base_dn, DN)
-
- # check if migration mode is enabled
- if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE':
- return dict(result={}, failed={}, enabled=False, compat=True)
-
- # connect to DS
- ds_ldap = ldap2(self.api, ldap_uri=ldapuri)
-
- cacert = None
- if options.get('cacertfile') is not None:
- #store CA cert into file
- tmp_ca_cert_f = write_tmp_file(options['cacertfile'])
- cacert = tmp_ca_cert_f.name
-
- #start TLS connection
- ds_ldap.connect(bind_dn=options['binddn'], bind_pw=bindpw,
- tls_cacertfile=cacert)
-
- tmp_ca_cert_f.close()
- else:
- ds_ldap.connect(bind_dn=options['binddn'], bind_pw=bindpw)
-
- #check whether the compat plugin is enabled
- if not options.get('compat'):
- try:
- ldap.get_entry(DN(('cn', 'compat'), (api.env.basedn)))
- return dict(result={}, failed={}, enabled=True, compat=False)
- except errors.NotFound:
- pass
-
- if not ds_base_dn:
- # retrieve base DN from remote LDAP server
- entries, truncated = ds_ldap.find_entries(
- '', ['namingcontexts', 'defaultnamingcontext'], DN(''),
- ds_ldap.SCOPE_BASE, size_limit=-1, time_limit=0,
- )
- if 'defaultnamingcontext' in entries[0]:
- ds_base_dn = DN(entries[0]['defaultnamingcontext'][0])
- assert isinstance(ds_base_dn, DN)
- else:
- try:
- ds_base_dn = DN(entries[0]['namingcontexts'][0])
- assert isinstance(ds_base_dn, DN)
- except (IndexError, KeyError) as e:
- raise Exception(str(e))
-
- # migrate!
- (migrated, failed) = self.migrate(
- ldap, config, ds_ldap, ds_base_dn, options
- )
-
- return dict(result=migrated, failed=failed, enabled=True, compat=True)
diff --git a/ipalib/plugins/misc.py b/ipalib/plugins/misc.py
deleted file mode 100644
index 0628bb19b..000000000
--- a/ipalib/plugins/misc.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Authors:
-# Jason Gerard DeRose <jderose@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, 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 re
-from ipalib import LocalOrRemote, _, ngettext
-from ipalib.output import Output, summary
-from ipalib import Flag
-from ipalib.plugable import Registry
-
-__doc__ = _("""
-Misc plug-ins
-""")
-
-register = Registry()
-
-# FIXME: We should not let env return anything in_server
-# when mode == 'production'. This would allow an attacker to see the
-# configuration of the server, potentially revealing compromising
-# information. However, it's damn handy for testing/debugging.
-
-
-@register()
-class env(LocalOrRemote):
- __doc__ = _('Show environment variables.')
-
- msg_summary = _('%(count)d variables')
-
- takes_args = (
- 'variables*',
- )
-
- takes_options = LocalOrRemote.takes_options + (
- Flag('all',
- cli_name='all',
- doc=_('retrieve and print all attributes from the server. Affects command output.'),
- exclude='webui',
- flags=['no_option', 'no_output'],
- default=True,
- ),
- )
-
- has_output = (
- Output('result',
- type=dict,
- doc=_('Dictionary mapping variable name to value'),
- ),
- Output('total',
- type=int,
- doc=_('Total number of variables env (>= count)'),
- flags=['no_display'],
- ),
- Output('count',
- type=int,
- doc=_('Number of variables returned (<= total)'),
- flags=['no_display'],
- ),
- summary,
- )
-
- def __find_keys(self, variables):
- keys = set()
- for query in variables:
- if '*' in query:
- pat = re.compile(query.replace('*', '.*') + '$')
- for key in self.env:
- if pat.match(key):
- keys.add(key)
- elif query in self.env:
- keys.add(query)
- return keys
-
- def execute(self, variables=None, **options):
- if variables is None:
- keys = self.env
- else:
- keys = self.__find_keys(variables)
- ret = dict(
- result=dict(
- (key, self.env[key]) for key in keys
- ),
- count=len(keys),
- total=len(self.env),
- )
- if len(keys) > 1:
- ret['summary'] = self.msg_summary % ret
- else:
- ret['summary'] = None
- return ret
-
-
-
-@register()
-class plugins(LocalOrRemote):
- __doc__ = _('Show all loaded plugins.')
-
- msg_summary = ngettext(
- '%(count)d plugin loaded', '%(count)d plugins loaded', 0
- )
-
- takes_options = LocalOrRemote.takes_options + (
- Flag('all',
- cli_name='all',
- doc=_('retrieve and print all attributes from the server. Affects command output.'),
- exclude='webui',
- flags=['no_option', 'no_output'],
- default=True,
- ),
- )
-
- has_output = (
- Output('result', dict, 'Dictionary mapping plugin names to bases'),
- Output('count',
- type=int,
- doc=_('Number of plugins loaded'),
- ),
- summary,
- )
-
- def execute(self, **options):
- return dict(
- result=dict(self.api.plugins),
- )
diff --git a/ipalib/plugins/netgroup.py b/ipalib/plugins/netgroup.py
deleted file mode 100644
index f76a0ba3a..000000000
--- a/ipalib/plugins/netgroup.py
+++ /dev/null
@@ -1,387 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# 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 ipalib import api, errors
-from ipalib import Str, StrEnum, Flag
-from ipalib.plugable import Registry
-from .baseldap import (
- external_host_param,
- add_external_pre_callback,
- add_external_post_callback,
- remove_external_post_callback,
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember)
-from ipalib import _, ngettext
-from .hbacrule import is_all
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Netgroups
-
-A netgroup is a group used for permission checking. It can contain both
-user and host values.
-
-EXAMPLES:
-
- Add a new netgroup:
- ipa netgroup-add --desc="NFS admins" admins
-
- Add members to the netgroup:
- ipa netgroup-add-member --users=tuser1 --users=tuser2 admins
-
- Remove a member from the netgroup:
- ipa netgroup-remove-member --users=tuser2 admins
-
- Display information about a netgroup:
- ipa netgroup-show admins
-
- Delete a netgroup:
- ipa netgroup-del admins
-""")
-
-register = Registry()
-
-NETGROUP_PATTERN='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*$'
-NETGROUP_PATTERN_ERRMSG='may only include letters, numbers, _, -, and .'
-
-# according to most common use cases the netgroup pattern should fit
-# also the nisdomain pattern
-NISDOMAIN_PATTERN=NETGROUP_PATTERN
-NISDOMAIN_PATTERN_ERRMSG=NETGROUP_PATTERN_ERRMSG
-
-output_params = (
- Str('memberuser_user?',
- label='Member User',
- ),
- Str('memberuser_group?',
- label='Member Group',
- ),
- Str('memberhost_host?',
- label=_('Member Host'),
- ),
- Str('memberhost_hostgroup?',
- label='Member Hostgroup',
- ),
- )
-
-
-@register()
-class netgroup(LDAPObject):
- """
- Netgroup object.
- """
- container_dn = api.env.container_netgroup
- object_name = _('netgroup')
- object_name_plural = _('netgroups')
- object_class = ['ipaobject', 'ipaassociation', 'ipanisnetgroup']
- permission_filter_objectclasses = ['ipanisnetgroup']
- search_attributes = [
- 'cn', 'description', 'memberof', 'externalhost', 'nisdomainname',
- 'memberuser', 'memberhost', 'member', 'usercategory', 'hostcategory',
- ]
- default_attributes = [
- 'cn', 'description', 'memberof', 'externalhost', 'nisdomainname',
- 'memberuser', 'memberhost', 'member', 'memberindirect',
- 'usercategory', 'hostcategory',
- ]
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- attribute_members = {
- 'member': ['netgroup'],
- 'memberof': ['netgroup'],
- 'memberindirect': ['netgroup'],
- 'memberuser': ['user', 'group'],
- 'memberhost': ['host', 'hostgroup'],
- }
- relationships = {
- 'member': ('Member', '', 'no_'),
- 'memberof': ('Member Of', 'in_', 'not_in_'),
- 'memberindirect': (
- 'Indirect Member', None, 'no_indirect_'
- ),
- 'memberuser': ('Member', '', 'no_'),
- 'memberhost': ('Member', '', 'no_'),
- }
- managed_permissions = {
- 'System: Read Netgroups': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'description', 'hostcategory', 'ipaenabledflag',
- 'ipauniqueid', 'nisdomainname', 'usercategory', 'objectclass',
- },
- },
- 'System: Read Netgroup Membership': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'externalhost', 'member', 'memberof', 'memberuser',
- 'memberhost', 'objectclass',
- },
- },
- 'System: Add Netgroups': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Add netgroups";allow (add) groupdn = "ldap:///cn=Add netgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Netgroups Administrators'},
- },
- 'System: Modify Netgroup Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'externalhost', 'member', 'memberhost', 'memberuser'
- },
- 'replaces': [
- '(targetattr = "memberhost || externalhost || memberuser || member")(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Modify netgroup membership";allow (write) groupdn = "ldap:///cn=Modify netgroup membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Netgroups Administrators'},
- },
- 'System: Modify Netgroups': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'description'},
- 'replaces': [
- '(targetattr = "description")(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0; acl "permission:Modify netgroups";allow (write) groupdn = "ldap:///cn=Modify netgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Netgroups Administrators'},
- },
- 'System: Remove Netgroups': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=ng,cn=alt,$SUFFIX")(version 3.0;acl "permission:Remove netgroups";allow (delete) groupdn = "ldap:///cn=Remove netgroups,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Netgroups Administrators'},
- },
- 'System: Read Netgroup Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=ng', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'membernisnetgroup', 'nisnetgrouptriple',
- },
- },
- }
-
- label = _('Netgroups')
- label_singular = _('Netgroup')
-
- takes_params = (
- Str('cn',
- pattern=NETGROUP_PATTERN,
- pattern_errmsg=NETGROUP_PATTERN_ERRMSG,
- cli_name='name',
- label=_('Netgroup name'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Netgroup description'),
- ),
- Str('nisdomainname?',
- pattern=NISDOMAIN_PATTERN,
- pattern_errmsg=NISDOMAIN_PATTERN_ERRMSG,
- cli_name='nisdomain',
- label=_('NIS domain name'),
- ),
- Str('ipauniqueid?',
- cli_name='uuid',
- label='IPA unique ID',
- doc=_('IPA unique ID'),
- flags=['no_create', 'no_update'],
- ),
- StrEnum('usercategory?',
- cli_name='usercat',
- label=_('User category'),
- doc=_('User category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('hostcategory?',
- cli_name='hostcat',
- label=_('Host category'),
- doc=_('Host category the rule applies to'),
- values=(u'all', ),
- ),
- external_host_param,
- )
-
-
-@register()
-class netgroup_add(LDAPCreate):
- __doc__ = _('Add a new netgroup.')
-
- has_output_params = LDAPCreate.has_output_params + output_params
- msg_summary = _('Added netgroup "%(value)s"')
-
- msg_collision = _(u'hostgroup with name "%s" already exists. ' \
- u'Hostgroups and netgroups share a common namespace')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- entry_attrs.setdefault('nisdomainname', self.api.env.domain)
-
- try:
- test_dn = self.obj.get_dn(keys[-1])
- netgroup = ldap.get_entry(test_dn, ['objectclass'])
- if 'mepManagedEntry' in netgroup.get('objectclass', []):
- raise errors.DuplicateEntry(message=unicode(self.msg_collision % keys[-1]))
- else:
- self.obj.handle_duplicate_entry(*keys)
- except errors.NotFound:
- pass
-
- try:
- # when enabled, a managed netgroup is created for every hostgroup
- # make sure that we don't create a collision if the plugin is
- # (temporarily) disabled
- api.Object['hostgroup'].get_dn_if_exists(keys[-1])
- raise errors.DuplicateEntry(message=unicode(self.msg_collision % keys[-1]))
- except errors.NotFound:
- pass
-
- return dn
-
-
-@register()
-class netgroup_del(LDAPDelete):
- __doc__ = _('Delete a netgroup.')
-
- msg_summary = _('Deleted netgroup "%(value)s"')
-
-
-
-@register()
-class netgroup_mod(LDAPUpdate):
- __doc__ = _('Modify a netgroup.')
-
- has_output_params = LDAPUpdate.has_output_params + output_params
- msg_summary = _('Modified netgroup "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, attrs_list)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(options, 'usercategory') and 'memberuser' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_("user category cannot be set to 'all' while there are allowed users"))
- if is_all(options, 'hostcategory') and 'memberhost' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=_("host category cannot be set to 'all' while there are allowed hosts"))
- return dn
-
-
-@register()
-class netgroup_find(LDAPSearch):
- __doc__ = _('Search for a netgroup.')
-
- member_attributes = ['member', 'memberuser', 'memberhost', 'memberof']
- has_output_params = LDAPSearch.has_output_params + output_params
- msg_summary = ngettext(
- '%(count)d netgroup matched', '%(count)d netgroups matched', 0
- )
-
- takes_options = LDAPSearch.takes_options + (
- Flag('private',
- exclude='webui',
- flags=['no_option', 'no_output'],
- ),
- Flag('managed',
- cli_name='managed',
- doc=_('search for managed groups'),
- default_from=lambda private: private,
- ),
- )
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- # Do not display private mepManagedEntry netgroups by default
- # If looking for managed groups, we need to omit the negation search filter
-
- search_kw = {}
- search_kw['objectclass'] = ['mepManagedEntry']
- if not options['managed']:
- local_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_NONE)
- else:
- local_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
- filter = ldap.combine_filters((local_filter, filter), rules=ldap.MATCH_ALL)
- return (filter, base_dn, scope)
-
-
-@register()
-class netgroup_show(LDAPRetrieve):
- __doc__ = _('Display information about a netgroup.')
-
- has_output_params = LDAPRetrieve.has_output_params + output_params
-
-
-@register()
-class netgroup_add_member(LDAPAddMember):
- __doc__ = _('Add members to a netgroup.')
-
- member_attributes = ['memberuser', 'memberhost', 'member']
- has_output_params = LDAPAddMember.has_output_params + output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- return add_external_pre_callback('host', ldap, dn, keys, options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return add_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberhost',
- membertype='host',
- externalattr='externalhost')
-
-
-@register()
-class netgroup_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from a netgroup.')
-
- member_attributes = ['memberuser', 'memberhost', 'member']
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return remove_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberhost',
- membertype='host',
- externalattr='externalhost')
diff --git a/ipalib/plugins/otp.py b/ipalib/plugins/otp.py
deleted file mode 100644
index 306c87388..000000000
--- a/ipalib/plugins/otp.py
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
-#
-
-from ipalib.text import _
-
-__doc__ = _('One time password commands')
diff --git a/ipalib/plugins/otpconfig.py b/ipalib/plugins/otpconfig.py
deleted file mode 100644
index c7710468f..000000000
--- a/ipalib/plugins/otpconfig.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# 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/>.
-
-from ipalib import _, api, Int
-from ipalib.plugable import Registry
-from .baseldap import DN, LDAPObject, LDAPUpdate, LDAPRetrieve
-
-__doc__ = _("""
-OTP configuration
-
-Manage the default values that IPA uses for OTP tokens.
-
-EXAMPLES:
-
- Show basic OTP configuration:
- ipa otpconfig-show
-
- Show all OTP configuration options:
- ipa otpconfig-show --all
-
- Change maximum TOTP authentication window to 10 minutes:
- ipa otpconfig-mod --totp-auth-window=600
-
- Change maximum TOTP synchronization window to 12 hours:
- ipa otpconfig-mod --totp-sync-window=43200
-
- Change maximum HOTP authentication window to 5:
- ipa hotpconfig-mod --hotp-auth-window=5
-
- Change maximum HOTP synchronization window to 50:
- ipa hotpconfig-mod --hotp-sync-window=50
-""")
-
-register = Registry()
-
-topic = 'otp'
-
-
-@register()
-class otpconfig(LDAPObject):
- object_name = _('OTP configuration options')
- default_attributes = [
- 'ipatokentotpauthwindow',
- 'ipatokentotpsyncwindow',
- 'ipatokenhotpauthwindow',
- 'ipatokenhotpsyncwindow',
- ]
-
- container_dn = DN(('cn', 'otp'), ('cn', 'etc'))
- permission_filter_objectclasses = ['ipatokenotpconfig']
- managed_permissions = {
- 'System: Read OTP Configuration': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'ipatokentotpauthwindow', 'ipatokentotpsyncwindow',
- 'ipatokenhotpauthwindow', 'ipatokenhotpsyncwindow',
- 'cn',
- },
- },
- }
-
- label = _('OTP Configuration')
- label_singular = _('OTP Configuration')
-
- takes_params = (
- Int('ipatokentotpauthwindow',
- cli_name='totp_auth_window',
- label=_('TOTP authentication Window'),
- doc=_('TOTP authentication time variance (seconds)'),
- minvalue=5,
- ),
- Int('ipatokentotpsyncwindow',
- cli_name='totp_sync_window',
- label=_('TOTP Synchronization Window'),
- doc=_('TOTP synchronization time variance (seconds)'),
- minvalue=5,
- ),
- Int('ipatokenhotpauthwindow',
- cli_name='hotp_auth_window',
- label=_('HOTP Authentication Window'),
- doc=_('HOTP authentication skip-ahead'),
- minvalue=1,
- ),
- Int('ipatokenhotpsyncwindow',
- cli_name='hotp_sync_window',
- label=_('HOTP Synchronization Window'),
- doc=_('HOTP synchronization skip-ahead'),
- minvalue=1,
- ),
- )
-
- def get_dn(self, *keys, **kwargs):
- return self.container_dn + api.env.basedn
-
-
-@register()
-class otpconfig_mod(LDAPUpdate):
- __doc__ = _('Modify OTP configuration options.')
-
-
-@register()
-class otpconfig_show(LDAPRetrieve):
- __doc__ = _('Show the current OTP configuration.')
diff --git a/ipalib/plugins/otptoken.py b/ipalib/plugins/otptoken.py
deleted file mode 100644
index fda05ce0b..000000000
--- a/ipalib/plugins/otptoken.py
+++ /dev/null
@@ -1,464 +0,0 @@
-# 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 .baseldap import LDAPObject, LDAPAddMember, LDAPRemoveMember
-from .baseldap import LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve
-from ipalib import api, Int, Str, Bool, DateTime, Flag, Bytes, IntEnum, StrEnum, _, ngettext
-from ipalib.plugable import Registry
-from ipalib.errors import (
- PasswordMismatch,
- ConversionError,
- NotFound,
- ValidationError)
-from ipalib.request import context
-from ipapython.dn import DN
-
-import base64
-import uuid
-import os
-
-import six
-from six.moves import urllib
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-OTP Tokens
-""") + _("""
-Manage OTP tokens.
-""") + _("""
-IPA supports the use of OTP tokens for multi-factor authentication. This
-code enables the management of OTP tokens.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Add a new token:
- ipa otptoken-add --type=totp --owner=jdoe --desc="My soft token"
-""") + _("""
- Examine the token:
- ipa otptoken-show a93db710-a31a-4639-8647-f15b2c70b78a
-""") + _("""
- Change the vendor:
- ipa otptoken-mod a93db710-a31a-4639-8647-f15b2c70b78a --vendor="Red Hat"
-""") + _("""
- Delete a token:
- ipa otptoken-del a93db710-a31a-4639-8647-f15b2c70b78a
-""")
-
-register = Registry()
-
-topic = 'otp'
-
-TOKEN_TYPES = {
- u'totp': ['ipatokentotpclockoffset', 'ipatokentotptimestep'],
- u'hotp': ['ipatokenhotpcounter']
-}
-
-# NOTE: For maximum compatibility, KEY_LENGTH % 5 == 0
-KEY_LENGTH = 20
-
-class OTPTokenKey(Bytes):
- """A binary password type specified in base32."""
-
- password = True
-
- kwargs = Bytes.kwargs + (
- ('confirm', bool, True),
- )
-
- def _convert_scalar(self, value, index=None):
- if isinstance(value, (tuple, list)) and len(value) == 2:
- (p1, p2) = value
- if p1 != p2:
- raise PasswordMismatch(name=self.name)
- value = p1
-
- if isinstance(value, unicode):
- try:
- value = base64.b32decode(value, True)
- except TypeError as e:
- raise ConversionError(name=self.name, error=str(e))
-
- return super(OTPTokenKey, self)._convert_scalar(value)
-
-def _convert_owner(userobj, entry_attrs, options):
- if 'ipatokenowner' in entry_attrs and not options.get('raw', False):
- entry_attrs['ipatokenowner'] = [userobj.get_primary_key_from_dn(o)
- for o in entry_attrs['ipatokenowner']]
-
-def _normalize_owner(userobj, entry_attrs):
- owner = entry_attrs.get('ipatokenowner', None)
- if owner:
- try:
- entry_attrs['ipatokenowner'] = userobj._normalize_manager(owner)[0]
- except NotFound:
- userobj.handle_not_found(owner)
-
-def _check_interval(not_before, not_after):
- if not_before and not_after:
- return not_before <= not_after
- return True
-
-def _set_token_type(entry_attrs, **options):
- klasses = [x.lower() for x in entry_attrs.get('objectclass', [])]
- for ttype in TOKEN_TYPES.keys():
- cls = 'ipatoken' + ttype
- if cls.lower() in klasses:
- entry_attrs['type'] = ttype.upper()
-
- if not options.get('all', False) or options.get('pkey_only', False):
- entry_attrs.pop('objectclass', None)
-
-@register()
-class otptoken(LDAPObject):
- """
- OTP Token object.
- """
- container_dn = api.env.container_otp
- object_name = _('OTP token')
- object_name_plural = _('OTP tokens')
- object_class = ['ipatoken']
- possible_objectclasses = ['ipatokentotp', 'ipatokenhotp']
- default_attributes = [
- 'ipatokenuniqueid', 'description', 'ipatokenowner',
- 'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter',
- 'ipatokenvendor', 'ipatokenmodel', 'ipatokenserial', 'managedby'
- ]
- attribute_members = {
- 'managedby': ['user'],
- }
- relationships = {
- 'managedby': ('Managed by', 'man_by_', 'not_man_by_'),
- }
- rdn_is_primary_key = True
-
- label = _('OTP Tokens')
- label_singular = _('OTP Token')
-
- takes_params = (
- Str('ipatokenuniqueid',
- cli_name='id',
- label=_('Unique ID'),
- primary_key=True,
- flags=('optional_create'),
- ),
- StrEnum('type?',
- label=_('Type'),
- doc=_('Type of the token'),
- default=u'totp',
- autofill=True,
- values=tuple(list(TOKEN_TYPES) + [x.upper() for x in TOKEN_TYPES]),
- flags=('virtual_attribute', 'no_update'),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Token description (informational only)'),
- ),
- Str('ipatokenowner?',
- cli_name='owner',
- label=_('Owner'),
- doc=_('Assigned user of the token (default: self)'),
- ),
- Str('managedby_user?',
- label=_('Manager'),
- doc=_('Assigned manager of the token (default: self)'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Bool('ipatokendisabled?',
- cli_name='disabled',
- label=_('Disabled'),
- doc=_('Mark the token as disabled (default: false)')
- ),
- DateTime('ipatokennotbefore?',
- cli_name='not_before',
- label=_('Validity start'),
- doc=_('First date/time the token can be used'),
- ),
- DateTime('ipatokennotafter?',
- cli_name='not_after',
- label=_('Validity end'),
- doc=_('Last date/time the token can be used'),
- ),
- Str('ipatokenvendor?',
- cli_name='vendor',
- label=_('Vendor'),
- doc=_('Token vendor name (informational only)'),
- ),
- Str('ipatokenmodel?',
- cli_name='model',
- label=_('Model'),
- doc=_('Token model (informational only)'),
- ),
- Str('ipatokenserial?',
- cli_name='serial',
- label=_('Serial'),
- doc=_('Token serial (informational only)'),
- ),
- OTPTokenKey('ipatokenotpkey?',
- cli_name='key',
- label=_('Key'),
- doc=_('Token secret (Base32; default: random)'),
- default_from=lambda: os.urandom(KEY_LENGTH),
- autofill=True,
- flags=('no_display', 'no_update', 'no_search'),
- ),
- StrEnum('ipatokenotpalgorithm?',
- cli_name='algo',
- label=_('Algorithm'),
- doc=_('Token hash algorithm'),
- default=u'sha1',
- autofill=True,
- flags=('no_update'),
- values=(u'sha1', u'sha256', u'sha384', u'sha512'),
- ),
- IntEnum('ipatokenotpdigits?',
- cli_name='digits',
- label=_('Digits'),
- doc=_('Number of digits each token code will have'),
- values=(6, 8),
- default=6,
- autofill=True,
- flags=('no_update'),
- ),
- Int('ipatokentotpclockoffset?',
- cli_name='offset',
- label=_('Clock offset'),
- doc=_('TOTP token / FreeIPA server time difference'),
- default=0,
- autofill=True,
- flags=('no_update'),
- ),
- Int('ipatokentotptimestep?',
- cli_name='interval',
- label=_('Clock interval'),
- doc=_('Length of TOTP token code validity'),
- default=30,
- autofill=True,
- minvalue=5,
- flags=('no_update'),
- ),
- Int('ipatokenhotpcounter?',
- cli_name='counter',
- label=_('Counter'),
- doc=_('Initial counter for the HOTP token'),
- default=0,
- autofill=True,
- minvalue=0,
- flags=('no_update'),
- ),
- )
-
-
-@register()
-class otptoken_add(LDAPCreate):
- __doc__ = _('Add a new OTP token.')
- msg_summary = _('Added OTP token "%(value)s"')
-
- takes_options = LDAPCreate.takes_options + (
- Flag('qrcode?', label=_('(deprecated)'), flags=('no_option')),
- Flag('no_qrcode', label=_('Do not display QR code'), default=False),
- )
-
- has_output_params = LDAPCreate.has_output_params + (
- Str('uri?', label=_('URI')),
- )
-
- def execute(self, ipatokenuniqueid=None, **options):
- return super(otptoken_add, self).execute(ipatokenuniqueid, **options)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- # Fill in a default UUID when not specified.
- if entry_attrs.get('ipatokenuniqueid', None) is None:
- entry_attrs['ipatokenuniqueid'] = str(uuid.uuid4())
- dn = DN("ipatokenuniqueid=%s" % entry_attrs['ipatokenuniqueid'], dn)
-
- if not _check_interval(options.get('ipatokennotbefore', None),
- options.get('ipatokennotafter', None)):
- raise ValidationError(name='not_after',
- error='is before the validity start')
-
- # Set the object class and defaults for specific token types
- options['type'] = options['type'].lower()
- entry_attrs['objectclass'] = otptoken.object_class + ['ipatoken' + options['type']]
- for ttype, tattrs in TOKEN_TYPES.items():
- if ttype != options['type']:
- for tattr in tattrs:
- if tattr in entry_attrs:
- del entry_attrs[tattr]
-
- # If owner was not specified, default to the person adding this token.
- # If managedby was not specified, attempt a sensible default.
- if 'ipatokenowner' not in entry_attrs or 'managedby' not in entry_attrs:
- result = self.api.Command.user_find(
- whoami=True, no_members=False)['result']
- if result:
- cur_uid = result[0]['uid'][0]
- prev_uid = entry_attrs.setdefault('ipatokenowner', cur_uid)
- if cur_uid == prev_uid:
- entry_attrs.setdefault('managedby', result[0]['dn'])
-
- # Resolve the owner's dn
- _normalize_owner(self.api.Object.user, entry_attrs)
-
- # Get the issuer for the URI
- owner = entry_attrs.get('ipatokenowner', None)
- issuer = api.env.realm
- if owner is not None:
- try:
- issuer = ldap.get_entry(owner, ['krbprincipalname'])['krbprincipalname'][0]
- except (NotFound, IndexError):
- pass
-
- # Build the URI parameters
- args = {}
- args['issuer'] = issuer
- args['secret'] = base64.b32encode(entry_attrs['ipatokenotpkey'])
- args['digits'] = entry_attrs['ipatokenotpdigits']
- args['algorithm'] = entry_attrs['ipatokenotpalgorithm'].upper()
- if options['type'] == 'totp':
- args['period'] = entry_attrs['ipatokentotptimestep']
- elif options['type'] == 'hotp':
- args['counter'] = entry_attrs['ipatokenhotpcounter']
-
- # Build the URI
- label = urllib.parse.quote(entry_attrs['ipatokenuniqueid'])
- parameters = urllib.parse.urlencode(args)
- uri = u'otpauth://%s/%s:%s?%s' % (options['type'], issuer, label, parameters)
- setattr(context, 'uri', uri)
-
- attrs_list.append("objectclass")
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- entry_attrs['uri'] = getattr(context, 'uri')
- _set_token_type(entry_attrs, **options)
- _convert_owner(self.api.Object.user, entry_attrs, options)
- return super(otptoken_add, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
-
-
-@register()
-class otptoken_del(LDAPDelete):
- __doc__ = _('Delete an OTP token.')
- msg_summary = _('Deleted OTP token "%(value)s"')
-
-
-@register()
-class otptoken_mod(LDAPUpdate):
- __doc__ = _('Modify a OTP token.')
- msg_summary = _('Modified OTP token "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- notafter_set = True
- notbefore = options.get('ipatokennotbefore', None)
- notafter = options.get('ipatokennotafter', None)
- # notbefore xor notafter, exactly one of them is not None
- if bool(notbefore) ^ bool(notafter):
- result = self.api.Command.otptoken_show(keys[-1])['result']
- if notbefore is None:
- notbefore = result.get('ipatokennotbefore', [None])[0]
- if notafter is None:
- notafter_set = False
- notafter = result.get('ipatokennotafter', [None])[0]
-
- if not _check_interval(notbefore, notafter):
- if notafter_set:
- raise ValidationError(name='not_after',
- error='is before the validity start')
- else:
- raise ValidationError(name='not_before',
- error='is after the validity end')
- _normalize_owner(self.api.Object.user, entry_attrs)
-
- # ticket #4681: if the owner of the token is changed and the
- # user also manages this token, then we should automatically
- # set the 'managedby' attribute to the new owner
- if 'ipatokenowner' in entry_attrs and 'managedby' not in entry_attrs:
- new_owner = entry_attrs.get('ipatokenowner', None)
- prev_entry = ldap.get_entry(dn, attrs_list=['ipatokenowner',
- 'managedby'])
- prev_owner = prev_entry.get('ipatokenowner', None)
- prev_managedby = prev_entry.get('managedby', None)
-
- if (new_owner != prev_owner) and (prev_owner == prev_managedby):
- entry_attrs.setdefault('managedby', new_owner)
-
- attrs_list.append("objectclass")
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- _set_token_type(entry_attrs, **options)
- _convert_owner(self.api.Object.user, entry_attrs, options)
- return super(otptoken_mod, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
-
-
-@register()
-class otptoken_find(LDAPSearch):
- __doc__ = _('Search for OTP token.')
- msg_summary = ngettext('%(count)d OTP token matched', '%(count)d OTP tokens matched', 0)
-
- def pre_callback(self, ldap, filters, attrs_list, *args, **kwargs):
- # This is a hack, but there is no other way to
- # replace the objectClass when searching
- type = kwargs.get('type', '')
- if type not in TOKEN_TYPES:
- type = ''
- filters = filters.replace("(objectclass=ipatoken)",
- "(objectclass=ipatoken%s)" % type)
-
- attrs_list.append("objectclass")
- return super(otptoken_find, self).pre_callback(ldap, filters, attrs_list, *args, **kwargs)
-
- def args_options_2_entry(self, *args, **options):
- entry = super(otptoken_find, self).args_options_2_entry(*args, **options)
- _normalize_owner(self.api.Object.user, entry)
- return entry
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for entry in entries:
- _set_token_type(entry, **options)
- _convert_owner(self.api.Object.user, entry, options)
- return super(otptoken_find, self).post_callback(ldap, entries, truncated, *args, **options)
-
-
-@register()
-class otptoken_show(LDAPRetrieve):
- __doc__ = _('Display information about an OTP token.')
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- attrs_list.append("objectclass")
- return super(otptoken_show, self).pre_callback(ldap, dn, attrs_list, *keys, **options)
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- _set_token_type(entry_attrs, **options)
- _convert_owner(self.api.Object.user, entry_attrs, options)
- return super(otptoken_show, self).post_callback(ldap, dn, entry_attrs, *keys, **options)
-
-@register()
-class otptoken_add_managedby(LDAPAddMember):
- __doc__ = _('Add users that can manage this token.')
-
- member_attributes = ['managedby']
-
-@register()
-class otptoken_remove_managedby(LDAPRemoveMember):
- __doc__ = _('Remove users that can manage this token.')
-
- member_attributes = ['managedby']
diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py
deleted file mode 100644
index c4e220815..000000000
--- a/ipalib/plugins/passwd.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, 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 ipalib import api, errors, krb_utils
-from ipalib import Command
-from ipalib import Str, Password
-from ipalib import _
-from ipalib import output
-from ipalib.plugable import Registry
-from .baseuser import validate_principal, normalize_principal
-from ipalib.request import context
-from ipapython.dn import DN
-
-__doc__ = _("""
-Set a user's password
-
-If someone other than a user changes that user's password (e.g., Helpdesk
-resets it) then the password will need to be changed the first time it
-is used. This is so the end-user is the only one who knows the password.
-
-The IPA password policy controls how often a password may be changed,
-what strength requirements exist, and the length of the password history.
-
-EXAMPLES:
-
- To reset your own password:
- ipa passwd
-
- To change another user's password:
- ipa passwd tuser1
-""")
-
-register = Registry()
-
-# We only need to prompt for the current password when changing a password
-# for yourself, but the parameter is still required
-MAGIC_VALUE = u'CHANGING_PASSWORD_FOR_ANOTHER_USER'
-
-def get_current_password(principal):
- """
- If the user is changing their own password then return None so the
- current password is prompted for, otherwise return a fixed value to
- be ignored later.
- """
- current_principal = krb_utils.get_principal()
- if current_principal == normalize_principal(principal):
- return None
- else:
- return MAGIC_VALUE
-
-@register()
-class passwd(Command):
- __doc__ = _("Set a user's password.")
-
- takes_args = (
- Str('principal', validate_principal,
- cli_name='user',
- label=_('User name'),
- primary_key=True,
- autofill=True,
- default_from=lambda: krb_utils.get_principal(),
- normalizer=lambda value: normalize_principal(value),
- ),
- Password('password',
- label=_('New Password'),
- ),
- Password('current_password',
- label=_('Current Password'),
- confirm=False,
- default_from=lambda principal: get_current_password(principal),
- autofill=True,
- sortorder=-1,
- ),
- )
-
- takes_options = (
- Password('otp?',
- label=_('OTP'),
- doc=_('One Time Password'),
- confirm=False,
- ),
- )
-
- has_output = output.standard_value
- msg_summary = _('Changed password for "%(value)s"')
-
- def execute(self, principal, password, current_password, **options):
- """
- Execute the passwd operation.
-
- The dn should not be passed as a keyword argument as it is constructed
- by this method.
-
- Returns the entry
-
- :param principal: The login name or principal of the user
- :param password: the new password
- :param current_password: the existing password, if applicable
- """
- ldap = self.api.Backend.ldap2
-
- entry_attrs = ldap.find_entry_by_attr(
- 'krbprincipalname', principal, 'posixaccount', [''],
- DN(api.env.container_user, api.env.basedn)
- )
-
- if principal == getattr(context, 'principal') and \
- current_password == MAGIC_VALUE:
- # No cheating
- self.log.warning('User attempted to change password using magic value')
- raise errors.ACIError(info=_('Invalid credentials'))
-
- if current_password == MAGIC_VALUE:
- ldap.modify_password(entry_attrs.dn, password)
- else:
- otp = options.get('otp')
- ldap.modify_password(entry_attrs.dn, password, current_password, otp)
-
- return dict(
- result=True,
- value=principal,
- )
-
diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py
deleted file mode 100644
index 9f19358da..000000000
--- a/ipalib/plugins/permission.py
+++ /dev/null
@@ -1,1395 +0,0 @@
-# Authors:
-# Petr Viktorin <pviktori@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/>.
-
-import re
-import traceback
-
-import six
-
-from . import baseldap
-from .privilege import validate_permission_to_privilege
-from ipalib import errors
-from ipalib.parameters import Str, StrEnum, DNParam, Flag
-from ipalib import api, _, ngettext
-from ipalib.plugable import Registry
-from ipalib.capabilities import client_has_capability
-from ipalib.aci import ACI
-from ipapython.dn import DN
-from ipalib.request import context
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Permissions
-""") + _("""
-A permission enables fine-grained delegation of rights. A permission is
-a human-readable wrapper around a 389-ds Access Control Rule,
-or instruction (ACI).
-A permission grants the right to perform a specific task such as adding a
-user, modifying a group, etc.
-""") + _("""
-A permission may not contain other permissions.
-""") + _("""
-* A permission grants access to read, write, add, delete, read, search,
- or compare.
-* A privilege combines similar permissions (for example all the permissions
- needed to add a user).
-* A role grants a set of privileges to users, groups, hosts or hostgroups.
-""") + _("""
-A permission is made up of a number of different parts:
-
-1. The name of the permission.
-2. The target of the permission.
-3. The rights granted by the permission.
-""") + _("""
-Rights define what operations are allowed, and may be one or more
-of the following:
-1. write - write one or more attributes
-2. read - read one or more attributes
-3. search - search on one or more attributes
-4. compare - compare one or more attributes
-5. add - add a new entry to the tree
-6. delete - delete an existing entry
-7. all - all permissions are granted
-""") + _("""
-Note the distinction between attributes and entries. The permissions are
-independent, so being able to add a user does not mean that the user will
-be editable.
-""") + _("""
-There are a number of allowed targets:
-1. subtree: a DN; the permission applies to the subtree under this DN
-2. target filter: an LDAP filter
-3. target: DN with possible wildcards, specifies entries permission applies to
-""") + _("""
-Additionally, there are the following convenience options.
-Setting one of these options will set the corresponding attribute(s).
-1. type: a type of object (user, group, etc); sets subtree and target filter.
-2. memberof: apply to members of a group; sets target filter
-3. targetgroup: grant access to modify a specific group (such as granting
- the rights to manage group membership); sets target.
-""") + _("""
-Managed permissions
-""") + _("""
-Permissions that come with IPA by default can be so-called "managed"
-permissions. These have a default set of attributes they apply to,
-but the administrator can add/remove individual attributes to/from the set.
-""") + _("""
-Deleting or renaming a managed permission, as well as changing its target,
-is not allowed.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Add a permission that grants the creation of users:
- ipa permission-add --type=user --permissions=add "Add Users"
-""") + _("""
- Add a permission that grants the ability to manage group membership:
- ipa permission-add --attrs=member --permissions=write --type=group "Manage Group Members"
-""")
-
-register = Registry()
-
-_DEPRECATED_OPTION_ALIASES = {
- 'permissions': 'ipapermright',
- 'filter': 'extratargetfilter',
- 'subtree': 'ipapermlocation',
-}
-
-KNOWN_FLAGS = {'SYSTEM', 'V2', 'MANAGED'}
-
-output_params = (
- Str('aci',
- label=_('ACI'),
- ),
-)
-
-
-def strip_ldap_prefix(uri):
- prefix = 'ldap:///'
- if not uri.startswith(prefix):
- raise ValueError('%r does not start with %r' % (uri, prefix))
- return uri[len(prefix):]
-
-
-def prevalidate_filter(ugettext, value):
- if not value.startswith('(') or not value.endswith(')'):
- return _('must be enclosed in parentheses')
-
-
-class DNOrURL(DNParam):
- """DN parameter that allows, and strips, a "ldap:///" prefix on input
-
- Used for ``subtree`` to maintain backward compatibility.
- """
-
- def _convert_scalar(self, value, index=None):
- if isinstance(value, six.string_types) and value.startswith('ldap:///'):
- value = strip_ldap_prefix(value)
- return super(DNOrURL, self)._convert_scalar(value)
-
-
-def validate_type(ugettext, typestr):
- try:
- obj = api.Object[typestr]
- except KeyError:
- return _('"%s" is not an object type') % typestr
- if not getattr(obj, 'permission_filter_objectclasses', None):
- return _('"%s" is not a valid permission type') % typestr
-
-
-def _disallow_colon(option):
- """Given a "cn" option, return a new "cn" option with ':' disallowed
-
- Used in permission-add and for --rename in permission-mod to prevent user
- from creating new permissions with ":" in the name.
- """
- return option.clone(
- pattern='^[-_ a-zA-Z0-9.]+$',
- pattern_errmsg="May only contain letters, numbers, -, _, ., and space",
- )
-
-
-@register()
-class permission(baseldap.LDAPObject):
- """
- Permission object.
- """
- container_dn = api.env.container_permission
- object_name = _('permission')
- object_name_plural = _('permissions')
- # For use the complete object_class list, including 'top', so
- # the updater doesn't try to delete 'top' every time.
- object_class = ['top', 'groupofnames', 'ipapermission', 'ipapermissionv2']
- permission_filter_objectclasses = ['ipapermission']
- default_attributes = ['cn', 'member', 'memberof',
- 'memberindirect', 'ipapermissiontype', 'objectclass',
- 'ipapermdefaultattr', 'ipapermincludedattr', 'ipapermexcludedattr',
- 'ipapermbindruletype', 'ipapermlocation', 'ipapermright',
- 'ipapermtargetfilter', 'ipapermtarget'
- ]
- attribute_members = {
- 'member': ['privilege'],
- 'memberindirect': ['role'],
- }
- rdn_is_primary_key = True
- managed_permissions = {
- 'System: Read Permissions': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'ipapermissiontype',
- 'o', 'objectclass', 'ou', 'owner', 'seealso',
- 'ipapermdefaultattr', 'ipapermincludedattr',
- 'ipapermexcludedattr', 'ipapermbindruletype', 'ipapermtarget',
- 'ipapermlocation', 'ipapermright', 'ipapermtargetfilter',
- 'member', 'memberof', 'memberuser', 'memberhost',
- },
- 'default_privileges': {'RBAC Readers'},
- },
- 'System: Read ACIs': {
- # Readable ACIs are needed for reading legacy permissions.
- 'non_object': True,
- 'ipapermlocation': api.env.basedn,
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {'aci'},
- 'default_privileges': {'RBAC Readers'},
- },
- 'System: Modify Privilege Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=permissions,cn=pbac,$SUFFIX")(version 3.0;acl "permission:Modify privilege membership";allow (write) groupdn = "ldap:///cn=Modify privilege membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Delegation Administrator'},
- },
- }
-
- label = _('Permissions')
- label_singular = _('Permission')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Permission name'),
- primary_key=True,
- pattern='^[-_ a-zA-Z0-9.:/]+$',
- pattern_errmsg="May only contain letters, numbers, "
- "-, _, ., :, /, and space",
- ),
- StrEnum(
- 'ipapermright*',
- cli_name='right',
- deprecated_cli_aliases={'permissions'},
- label=_('Granted rights'),
- doc=_('Rights to grant '
- '(read, search, compare, write, add, delete, all)'),
- values=(u'read', u'search', u'compare',
- u'write', u'add', u'delete', u'all'),
- flags={'ask_create'},
- ),
- Str('attrs*',
- label=_('Effective attributes'),
- doc=_('All attributes to which the permission applies'),
- flags={'virtual_attribute', 'allow_mod_for_managed_permission'},
- ),
- Str('ipapermincludedattr*',
- cli_name='includedattrs',
- label=_('Included attributes'),
- doc=_('User-specified attributes to which the permission applies'),
- flags={'no_create', 'allow_mod_for_managed_permission'},
- ),
- Str('ipapermexcludedattr*',
- cli_name='excludedattrs',
- label=_('Excluded attributes'),
- doc=_('User-specified attributes to which the permission '
- 'explicitly does not apply'),
- flags={'no_create', 'allow_mod_for_managed_permission'},
- ),
- Str('ipapermdefaultattr*',
- cli_name='defaultattrs',
- label=_('Default attributes'),
- doc=_('Attributes to which the permission applies by default'),
- flags={'no_create', 'no_update'},
- ),
- StrEnum(
- 'ipapermbindruletype',
- cli_name='bindtype',
- label=_('Bind rule type'),
- doc=_('Bind rule type'),
- autofill=True,
- values=(u'permission', u'all', u'anonymous'),
- default=u'permission',
- flags={'allow_mod_for_managed_permission'},
- ),
- DNOrURL(
- 'ipapermlocation?',
- cli_name='subtree',
- label=_('Subtree'),
- doc=_('Subtree to apply permissions to'),
- flags={'ask_create'},
- ),
- Str(
- 'extratargetfilter*', prevalidate_filter,
- cli_name='filter',
- label=_('Extra target filter'),
- doc=_('Extra target filter'),
- flags={'virtual_attribute'},
- ),
- Str(
- 'ipapermtargetfilter*', prevalidate_filter,
- cli_name='rawfilter',
- label=_('Raw target filter'),
- doc=_('All target filters, including those implied by '
- 'type and memberof'),
- ),
-
- DNParam(
- 'ipapermtarget?',
- cli_name='target',
- label=_('Target DN'),
- doc=_('Optional DN to apply the permission to '
- '(must be in the subtree, but may not yet exist)'),
- ),
-
- DNParam(
- 'ipapermtargetto?',
- cli_name='targetto',
- label=_('Target DN subtree'),
- doc=_('Optional DN subtree where an entry can be moved to '
- '(must be in the subtree, but may not yet exist)'),
- ),
-
- DNParam(
- 'ipapermtargetfrom?',
- cli_name='targetfrom',
- label=_('Origin DN subtree'),
- doc=_('Optional DN subtree from where an entry can be moved '
- '(must be in the subtree, but may not yet exist)'),
- ),
-
- Str('memberof*',
- label=_('Member of group'), # FIXME: Does this label make sense?
- doc=_('Target members of a group (sets memberOf targetfilter)'),
- flags={'ask_create', 'virtual_attribute'},
- ),
- Str('targetgroup?',
- label=_('Target group'),
- doc=_('User group to apply permissions to (sets target)'),
- flags={'ask_create', 'virtual_attribute'},
- ),
- Str(
- 'type?', validate_type,
- label=_('Type'),
- doc=_('Type of IPA object '
- '(sets subtree and objectClass targetfilter)'),
- flags={'ask_create', 'virtual_attribute'},
- ),
- ) + tuple(
- Str(old_name + '*',
- doc=_('Deprecated; use %s' % new_name),
- flags={'no_option', 'virtual_attribute'})
- for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items()
- )
-
- def reject_system(self, entry):
- """Raise if permission entry has unknown flags, or is a SYSTEM perm"""
- flags = entry.get('ipapermissiontype', [])
- for flag in flags:
- if flag not in KNOWN_FLAGS:
- raise errors.ACIError(
- info=_('Permission with unknown flag %s may not be '
- 'modified or removed') % flag)
- if list(flags) == [u'SYSTEM']:
- raise errors.ACIError(
- info=_('A SYSTEM permission may not be modified or removed'))
-
- def _get_filter_attr_info(self, entry):
- """Get information on filter-related virtual attributes
-
- Returns a dict with this information:
- 'implicit_targetfilters': targetfilters implied by memberof and type
- 'memberof': list of names of groups from memberof
- 'type': the type
- """
- ipapermtargetfilter = entry.get('ipapermtargetfilter', [])
- ipapermlocation = entry.single_value.get('ipapermlocation')
-
- implicit_targetfilters = set()
- result = {'implicit_targetfilters': implicit_targetfilters}
-
- # memberof
- memberof = []
- for targetfilter in ipapermtargetfilter:
- match = re.match('^\(memberof=(.*)\)$', targetfilter, re.I)
- if match:
- try:
- dn = DN(match.group(1))
- except ValueError:
- # Malformed DN; e.g. (memberof=*)
- continue
- groups_dn = DN(self.api.Object.group.container_dn,
- self.api.env.basedn)
- if dn[1:] == groups_dn[:] and dn[0].attr == 'cn':
- memberof.append(dn[0].value)
- implicit_targetfilters.add(match.group(0))
- if memberof:
- result['memberof'] = memberof
-
- # type
- if ipapermtargetfilter and ipapermlocation:
- for obj in self.api.Object():
- filt = self.make_type_filter(obj)
- if not filt:
- continue
-
- wantdn = DN(obj.container_dn, self.api.env.basedn)
- if DN(ipapermlocation) != wantdn:
- continue
-
- if filt in ipapermtargetfilter:
- result['type'] = [unicode(obj.name)]
- implicit_targetfilters.add(filt)
- break
-
- return result
-
- def postprocess_result(self, entry, options):
- """Update a permission entry for output (in place)
-
- :param entry: The entry to update
- :param options:
- Command options. Contains keys such as ``raw``, ``all``,
- ``pkey_only``, ``version``.
- """
- old_client = not client_has_capability(
- options['version'], 'permissions2')
-
- if not options.get('raw') and not options.get('pkey_only'):
- ipapermtargetfilter = entry.get('ipapermtargetfilter', [])
- ipapermtarget = entry.single_value.get('ipapermtarget')
-
- # targetgroup
- if ipapermtarget:
- dn = DN(ipapermtarget)
- if (dn[1:] == DN(self.api.Object.group.container_dn,
- self.api.env.basedn)[:] and
- dn[0].attr == 'cn' and dn[0].value != '*'):
- entry.single_value['targetgroup'] = dn[0].value
-
- filter_attr_info = self._get_filter_attr_info(entry)
- if 'type' in filter_attr_info:
- entry['type'] = filter_attr_info['type']
- if 'memberof' in filter_attr_info:
- entry['memberof'] = filter_attr_info['memberof']
- if 'implicit_targetfilters' in filter_attr_info:
- extratargetfilter = sorted(
- set(ipapermtargetfilter) -
- filter_attr_info['implicit_targetfilters'])
- if extratargetfilter:
- entry['extratargetfilter'] = extratargetfilter
-
- # old output names
- if old_client:
- for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items():
- if new_name in entry:
- entry[old_name] = entry[new_name]
- del entry[new_name]
-
- rights = entry.get('attributelevelrights')
- if rights:
- if 'ipapermtarget' in rights:
- rights['targetgroup'] = rights['ipapermtarget']
- if 'ipapermtargetfilter' in rights:
- rights['memberof'] = rights['ipapermtargetfilter']
-
- type_rights = set(rights['ipapermtargetfilter'])
- location_rights = set(rights.get('ipapermlocation', ''))
- type_rights.intersection_update(location_rights)
- rights['type'] = ''.join(sorted(
- type_rights, key=rights['ipapermtargetfilter'].index))
-
- if 'ipapermincludedattr' in rights:
- rights['attrs'] = ''.join(sorted(
- set(rights['ipapermincludedattr']) &
- set(rights.get('ipapermexcludedattr', '')),
- key=rights['ipapermincludedattr'].index))
-
- if old_client:
- for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items():
- if new_name in entry:
- rights[old_name] = rights[new_name]
- del rights[new_name]
-
- if options.get('raw'):
- # Retreive the ACI from LDAP to ensure we get the real thing
- try:
- acientry, acistring = self._get_aci_entry_and_string(entry)
- except errors.NotFound:
- if list(entry.get('ipapermissiontype')) == ['SYSTEM']:
- # SYSTEM permissions don't have normal ACIs
- pass
- else:
- raise
- else:
- entry.single_value['aci'] = acistring
- else:
- effective_attrs = self.get_effective_attrs(entry)
- if effective_attrs:
- entry['attrs'] = effective_attrs
- if (not options.get('all') and
- not entry.get('ipapermexcludedattr') and
- not entry.get('ipapermdefaultattr')):
- entry.pop('ipapermincludedattr', None)
-
- if old_client:
- # Legacy clients expect some attributes as a single value
- for attr in 'type', 'targetgroup', 'aci':
- if attr in entry:
- entry[attr] = entry.single_value[attr]
- # memberof was also single-valued, but not any more
- if entry.get('memberof'):
- joined_value = u', '.join(str(m) for m in entry['memberof'])
- entry['memberof'] = joined_value
- if 'subtree' in entry:
- # Legacy clients expect subtree as a URL
- dn = entry.single_value['subtree']
- entry['subtree'] = u'ldap:///%s' % dn
- if 'filter' in entry:
- # Legacy clients expect filter without parentheses
- new_filter = []
- for flt in entry['filter']:
- assert flt[0] == '(' and flt[-1] == ')'
- new_filter.append(flt[1:-1])
- entry['filter'] = new_filter
-
- if not options['raw'] and not options['all']:
- # Don't return the raw target filter by default
- entry.pop('ipapermtargetfilter', None)
-
- def get_effective_attrs(self, entry):
- attrs = set(entry.get('ipapermdefaultattr', ()))
- attrs.update(entry.get('ipapermincludedattr', ()))
- if ('read' in entry.get('ipapermright', ()) and
- 'objectclass' in (x.lower() for x in attrs)):
- # Add special-cased operational attributes
- # We want to allow reading these whenever reading the objectclass
- # is allowed.
- # (But they can still be excluded explicitly, at least in managed
- # permissions).
- attrs.update((u'entryusn', u'createtimestamp', u'modifytimestamp'))
- attrs.difference_update(entry.get('ipapermexcludedattr', ()))
- return sorted(attrs)
-
- def make_aci(self, entry):
- """Make an ACI string from the given permission entry"""
-
- aci_parts = []
- name = entry.single_value['cn']
-
- # targetattr
- attrs = self.get_effective_attrs(entry)
- if attrs:
- aci_parts.append("(targetattr = \"%s\")" % ' || '.join(attrs))
-
- # target
- ipapermtarget = entry.single_value.get('ipapermtarget')
- if ipapermtarget:
- aci_parts.append("(target = \"%s\")" %
- 'ldap:///%s' % ipapermtarget)
-
- # target_to
- ipapermtargetto = entry.single_value.get('ipapermtargetto')
- if ipapermtargetto:
- aci_parts.append("(target_to = \"%s\")" %
- 'ldap:///%s' % ipapermtargetto)
-
- # target_from
- ipapermtargetfrom = entry.single_value.get('ipapermtargetfrom')
- if ipapermtargetfrom:
- aci_parts.append("(target_from = \"%s\")" %
- 'ldap:///%s' % ipapermtargetfrom)
-
- # targetfilter
- ipapermtargetfilter = entry.get('ipapermtargetfilter')
- if ipapermtargetfilter:
- assert all(f.startswith('(') and f.endswith(')')
- for f in ipapermtargetfilter)
- if len(ipapermtargetfilter) == 1:
- filter = ipapermtargetfilter[0]
- else:
- filter = '(&%s)' % ''.join(sorted(ipapermtargetfilter))
- aci_parts.append("(targetfilter = \"%s\")" % filter)
-
- # version, name, rights, bind rule
- ipapermbindruletype = entry.single_value.get('ipapermbindruletype',
- 'permission')
- if ipapermbindruletype == 'permission':
- dn = DN(('cn', name), self.container_dn, self.api.env.basedn)
- bindrule = 'groupdn = "ldap:///%s"' % dn
- elif ipapermbindruletype == 'all':
- bindrule = 'userdn = "ldap:///all"'
- elif ipapermbindruletype == 'anonymous':
- bindrule = 'userdn = "ldap:///anyone"'
- else:
- raise ValueError(ipapermbindruletype)
-
- aci_parts.append('(version 3.0;acl "permission:%s";allow (%s) %s;)' % (
- name, ','.join(sorted(entry['ipapermright'])), bindrule))
-
- return ''.join(aci_parts)
-
- def add_aci(self, permission_entry):
- """Add the ACI coresponding to the given permission entry"""
- ldap = self.api.Backend.ldap2
- acistring = self.make_aci(permission_entry)
- location = permission_entry.single_value.get('ipapermlocation',
- self.api.env.basedn)
-
- self.log.debug('Adding ACI %r to %s' % (acistring, location))
- try:
- entry = ldap.get_entry(location, ['aci'])
- except errors.NotFound:
- raise errors.NotFound(reason=_('Entry %s not found') % location)
- entry.setdefault('aci', []).append(acistring)
- ldap.update_entry(entry)
-
- def remove_aci(self, permission_entry):
- """Remove the ACI corresponding to the given permission entry
-
- :return: tuple:
- - entry
- - removed ACI string, or None if none existed previously
- """
- return self._replace_aci(permission_entry)
-
- def update_aci(self, permission_entry, old_name=None):
- """Update the ACI corresponding to the given permission entry
-
- :return: tuple:
- - entry
- - removed ACI string, or None if none existed previously
- """
- new_acistring = self.make_aci(permission_entry)
- return self._replace_aci(permission_entry, old_name, new_acistring)
-
- def _replace_aci(self, permission_entry, old_name=None, new_acistring=None):
- """Replace ACI corresponding to permission_entry
-
- :param old_name: the old name of the permission, if different from new
- :param new_acistring: new ACI string; if None the ACI is just deleted
- :return: tuple:
- - entry
- - removed ACI string, or None if none existed previously
- """
- ldap = self.api.Backend.ldap2
- acientry, acistring = self._get_aci_entry_and_string(
- permission_entry, old_name, notfound_ok=True)
-
- # (pylint thinks `acientry` is just a dict, but it's an LDAPEntry)
- acidn = acientry.dn # pylint: disable=E1103
-
- if acistring is not None:
- self.log.debug('Removing ACI %r from %s' % (acistring, acidn))
- acientry['aci'].remove(acistring)
- if new_acistring:
- self.log.debug('Adding ACI %r to %s' % (new_acistring, acidn))
- acientry.setdefault('aci', []).append(new_acistring)
- try:
- ldap.update_entry(acientry)
- except errors.EmptyModlist:
- self.log.debug('No changes to ACI')
- return acientry, acistring
-
- def _get_aci_entry_and_string(self, permission_entry, name=None,
- notfound_ok=False, cached_acientry=None):
- """Get the entry and ACI corresponding to the permission entry
-
- :param name: The name of the permission, or None for the cn
- :param notfound_ok:
- If true, (acientry, None) will be returned on missing ACI, rather
- than raising exception
- :param cached_acientry: See upgrade_permission()
- """
- ldap = self.api.Backend.ldap2
- if name is None:
- name = permission_entry.single_value['cn']
- location = permission_entry.single_value.get('ipapermlocation',
- self.api.env.basedn)
- wanted_aciname = 'permission:%s' % name
-
- if (cached_acientry and
- cached_acientry.dn == location and
- 'aci' in cached_acientry):
- acientry = cached_acientry
- else:
- try:
- acientry = ldap.get_entry(location, ['aci'])
- except errors.NotFound:
- acientry = ldap.make_entry(location)
- acis = acientry.get('aci', ())
- for acistring in acis:
- try:
- aci = ACI(acistring)
- except SyntaxError as e:
- self.log.warning('Unparseable ACI %s: %s (at %s)',
- acistring, e, location)
- continue
- if aci.name == wanted_aciname:
- return acientry, acistring
- else:
- if notfound_ok:
- return acientry, None
- raise errors.NotFound(
- reason=_('The ACI for permission %(name)s was not found '
- 'in %(dn)s ') % {'name': name, 'dn': location})
-
- def upgrade_permission(self, entry, target_entry=None,
- output_only=False, cached_acientry=None):
- """Upgrade the given permission entry to V2, in-place
-
- The entry is only upgraded if it is a plain old-style permission,
- that is, it has no flags set.
-
- :param target_entry:
- If given, ``target_entry`` is filled from information taken
- from the ACI corresponding to ``entry``.
- If None, ``entry`` itself is filled
- :param output_only:
- If true, the flags & objectclass are not updated to V2.
- Used for the -find and -show commands.
- :param cached_acientry:
- Optional pre-retreived entry that contains the existing ACI.
- If it is None or its DN does not match the location DN,
- cached_acientry is ignored and the entry is retreived from LDAP.
- """
- if entry.get('ipapermissiontype'):
- # Only convert old-style, non-SYSTEM permissions -- i.e. no flags
- return
- base, acistring = self._get_aci_entry_and_string(
- entry, cached_acientry=cached_acientry)
-
- if not target_entry:
- target_entry = entry
-
- # The DN of old permissions is always basedn
- # (pylint thinks `base` is just a dict, but it's an LDAPEntry)
- assert base.dn == self.api.env.basedn, base # pylint: disable=E1103
-
- aci = ACI(acistring)
-
- if 'target' in aci.target:
- target_entry.single_value['ipapermtarget'] = DN(strip_ldap_prefix(
- aci.target['target']['expression']))
- if 'targetfilter' in aci.target:
- target_entry.single_value['ipapermtargetfilter'] = unicode(
- aci.target['targetfilter']['expression'])
- if aci.bindrule['expression'] == 'ldap:///all':
- target_entry.single_value['ipapermbindruletype'] = u'all'
- elif aci.bindrule['expression'] == 'ldap:///anyone':
- target_entry.single_value['ipapermbindruletype'] = u'anonymous'
- else:
- target_entry.single_value['ipapermbindruletype'] = u'permission'
- target_entry['ipapermright'] = aci.permissions
- if 'targetattr' in aci.target:
- target_entry['ipapermincludedattr'] = [
- unicode(a) for a in aci.target['targetattr']['expression']]
-
- if not output_only:
- target_entry['ipapermissiontype'] = ['SYSTEM', 'V2']
- if 'ipapermissionv2' not in entry['objectclass']:
- target_entry['objectclass'] = list(entry['objectclass']) + [
- u'ipapermissionv2']
-
- target_entry['ipapermlocation'] = [self.api.env.basedn]
-
- # Make sure we're not losing *any info* by the upgrade
- new_acistring = self.make_aci(target_entry)
- if not ACI(new_acistring).isequal(aci):
- raise ValueError('Cannot convert ACI, %r != %r' % (new_acistring,
- acistring))
-
- def make_type_filter(self, obj):
- """Make a filter for a --type based permission from an Object"""
- objectclasses = getattr(obj, 'permission_filter_objectclasses', None)
- if not objectclasses:
- return None
- filters = [u'(objectclass=%s)' % o for o in objectclasses]
- if len(filters) == 1:
- return filters[0]
- else:
- return '(|%s)' % ''.join(sorted(filters))
-
- def preprocess_options(self, options,
- return_filter_ops=False,
- merge_targetfilter=False):
- """Preprocess options (in-place)
-
- :param options: A dictionary of options
- :param return_filter_ops:
- If false, assumes there is no pre-existing entry;
- additional values of ipapermtargetfilter are added to options.
- If true, a dictionary of operations on ipapermtargetfilter is
- returned.
- These operations must be performed after the existing entry
- is retrieved.
- The dict has the following keys:
- - remove: list of regular expression objects;
- implicit values that match any of them should be removed
- - add: list of values to be added, after any removals
- :merge_targetfilter:
- If true, the extratargetfilter is copied into ipapermtargetfilter.
- """
-
- if 'extratargetfilter' in options:
- if 'ipapermtargetfilter' in options:
- raise errors.ValidationError(
- name='ipapermtargetfilter',
- error=_('cannot specify full target filter '
- 'and extra target filter simultaneously'))
- if merge_targetfilter:
- options['ipapermtargetfilter'] = options['extratargetfilter']
-
- filter_ops = {'add': [], 'remove': []}
-
- if options.get('subtree'):
- if isinstance(options['subtree'], (list, tuple)):
- [options['subtree']] = options['subtree']
- try:
- options['subtree'] = strip_ldap_prefix(options['subtree'])
- except ValueError:
- raise errors.ValidationError(
- name='subtree',
- error='does not start with "ldap:///"')
-
- # Handle old options
- for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items():
- if old_name in options:
- if client_has_capability(options['version'], 'permissions2'):
- raise errors.ValidationError(
- name=old_name,
- error=_('option was renamed; use %s') % new_name)
- if new_name in options:
- raise errors.ValidationError(
- name=old_name,
- error=(_('Cannot use %(old_name)s with %(new_name)s') %
- {'old_name': old_name, 'new_name': new_name}))
- options[new_name] = options[old_name]
- del options[old_name]
-
- # memberof
- if 'memberof' in options:
- filter_ops['remove'].append(re.compile(r'\(memberOf=.*\)', re.I))
- memberof = options.pop('memberof')
- for group in (memberof or ()):
- try:
- groupdn = self.api.Object.group.get_dn_if_exists(group)
- except errors.NotFound:
- raise errors.NotFound(
- reason=_('%s: group not found') % group)
- filter_ops['add'].append(u'(memberOf=%s)' % groupdn)
-
- # targetgroup
- if 'targetgroup' in options:
- targetgroup = options.pop('targetgroup')
- if targetgroup:
- if 'ipapermtarget' in options:
- raise errors.ValidationError(
- name='ipapermtarget',
- error=_('target and targetgroup are mutually exclusive'))
- try:
- groupdn = self.api.Object.group.get_dn_if_exists(targetgroup)
- except errors.NotFound:
- raise errors.NotFound(
- reason=_('%s: group not found') % targetgroup)
- options['ipapermtarget'] = groupdn
- else:
- if 'ipapermtarget' not in options:
- options['ipapermtarget'] = None
-
- # type
- if 'type' in options:
- objtype = options.pop('type')
- filter_ops['remove'].append(re.compile(r'\(objectclass=.*\)', re.I))
- filter_ops['remove'].append(re.compile(
- r'\(\|(\(objectclass=[^(]*\))+\)', re.I))
- if objtype:
- if 'ipapermlocation' in options:
- raise errors.ValidationError(
- name='ipapermlocation',
- error=_('subtree and type are mutually exclusive'))
- obj = self.api.Object[objtype.lower()]
- filt = self.make_type_filter(obj)
- if not filt:
- raise errors.ValidationError(
- _('"%s" is not a valid permission type') % objtype)
- filter_ops['add'].append(filt)
- container_dn = DN(obj.container_dn, self.api.env.basedn)
- options['ipapermlocation'] = container_dn
- else:
- if 'ipapermlocation' not in options:
- options['ipapermlocation'] = None
-
- if return_filter_ops:
- return filter_ops
- elif filter_ops['add']:
- options['ipapermtargetfilter'] = list(options.get(
- 'ipapermtargetfilter') or []) + filter_ops['add']
-
- def validate_permission(self, entry):
- ldap = self.Backend.ldap2
-
- # Rough filter validation by a search
- if entry.get('ipapermtargetfilter'):
- try:
- ldap.find_entries(
- filter=ldap.combine_filters(entry['ipapermtargetfilter'],
- rules='&'),
- base_dn=self.env.basedn,
- scope=ldap.SCOPE_BASE,
- size_limit=1)
- except errors.NotFound:
- pass
- except errors.BadSearchFilter:
- raise errors.ValidationError(
- name='ipapermtargetfilter',
- error=_('Bad search filter'))
-
- # Ensure location exists
- if entry.get('ipapermlocation'):
- location = DN(entry.single_value['ipapermlocation'])
- try:
- ldap.get_entry(location, attrs_list=[])
- except errors.NotFound:
- raise errors.ValidationError(
- name='ipapermlocation',
- error=_('Entry %s does not exist') % location)
-
- # Ensure there's something in the ACI's filter
- needed_attrs = (
- 'ipapermtarget', 'ipapermtargetfilter',
- 'ipapermincludedattr', 'ipapermexcludedattr', 'ipapermdefaultattr')
- if not any(v for a in needed_attrs for v in (entry.get(a) or ())):
- raise errors.ValidationError(
- name='target',
- error=_('there must be at least one target entry specifier '
- '(e.g. target, targetfilter, attrs)'))
-
- # Ensure there's a right
- if not entry.get('ipapermright'):
- raise errors.RequirementError(name='ipapermright')
-
-
-@register()
-class permission_add_noaci(baseldap.LDAPCreate):
- __doc__ = _('Add a system permission without an ACI (internal command)')
-
- msg_summary = _('Added permission "%(value)s"')
- NO_CLI = True
- has_output_params = baseldap.LDAPCreate.has_output_params + output_params
-
- takes_options = (
- Str('ipapermissiontype+',
- label=_('Permission flags'),
- ),
- )
-
- def get_options(self):
- perm_options = set(o.name for o in self.obj.takes_params)
- for option in super(permission_add_noaci, self).get_options():
- # From new options, only cn & ipapermissiontype are supported
- if option.name in ['ipapermissiontype']:
- yield option.clone()
- # Other options such as raw, version are supported
- elif option.name not in perm_options:
- yield option.clone()
-
- def pre_callback(self, ldap, dn, entry, attrs_list, *keys, **options):
- entry['ipapermissiontype'] = list(options['ipapermissiontype'])
- entry['objectclass'] = [oc for oc in entry['objectclass']
- if oc.lower() != 'ipapermissionv2']
- return dn
-
-
-@register()
-class permission_add(baseldap.LDAPCreate):
- __doc__ = _('Add a new permission.')
-
- msg_summary = _('Added permission "%(value)s"')
- has_output_params = baseldap.LDAPCreate.has_output_params + output_params
-
- # Need to override execute so that processed options apply to
- # the whole command, not just the callbacks
- def execute(self, *keys, **options):
- self.obj.preprocess_options(options, merge_targetfilter=True)
- return super(permission_add, self).execute(*keys, **options)
-
- def get_args(self):
- for arg in super(permission_add, self).get_args():
- if arg.name == 'cn':
- yield _disallow_colon(arg)
- else:
- yield arg
-
- def pre_callback(self, ldap, dn, entry, attrs_list, *keys, **options):
- entry['ipapermissiontype'] = ['SYSTEM', 'V2']
- entry['cn'] = list(keys)
- if not entry.get('ipapermlocation'):
- entry.setdefault('ipapermlocation', [api.env.basedn])
-
- if 'attrs' in options:
- if 'ipapermincludedattr' in options:
- raise errors.ValidationError(
- name='attrs',
- error=_('attrs and included attributes are '
- 'mutually exclusive'))
- entry['ipapermincludedattr'] = list(options.pop('attrs') or ())
-
- self.obj.validate_permission(entry)
- return dn
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- try:
- self.obj.add_aci(entry)
- except Exception as e:
- # Adding the ACI failed.
- # We want to be 100% sure the ACI is not there, so try to
- # remove it. (This is a no-op if the ACI was not added.)
- self.obj.remove_aci(entry)
- # Remove the entry.
- # The permission entry serves as a "lock" tho prevent
- # permission-add commands started at the same time from
- # interfering. As long as the entry is there, the other
- # permission-add will fail with DuplicateEntry.
- # So deleting entry ("releasing the lock") must be the last
- # thing we do here.
- try:
- self.api.Backend['ldap2'].delete_entry(entry)
- except errors.NotFound:
- pass
- if isinstance(e, errors.NotFound):
- # add_aci may raise NotFound if the subtree is only virtual
- # like cn=compat,SUFFIX and thus passes the LDAP get entry test
- location = DN(entry.single_value['ipapermlocation'])
- raise errors.ValidationError(
- name='ipapermlocation',
- error=_('Cannot store permission ACI to %s') % location)
- # Re-raise original exception
- raise
- self.obj.postprocess_result(entry, options)
- return dn
-
-
-@register()
-class permission_del(baseldap.LDAPDelete):
- __doc__ = _('Delete a permission.')
-
- msg_summary = _('Deleted permission "%(value)s"')
-
- takes_options = baseldap.LDAPDelete.takes_options + (
- Flag('force',
- label=_('Force'),
- flags={'no_option', 'no_output'},
- doc=_('force delete of SYSTEM permissions'),
- ),
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- try:
- entry = ldap.get_entry(dn, attrs_list=self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if not options.get('force'):
- self.obj.reject_system(entry)
- if entry.get('ipapermdefaultattr'):
- raise errors.ACIError(
- info=_('cannot delete managed permissions'))
-
- try:
- self.obj.remove_aci(entry)
- except errors.NotFound:
- errors.NotFound(
- reason=_('ACI of permission %s was not found') % keys[0])
-
- return dn
-
-
-@register()
-class permission_mod(baseldap.LDAPUpdate):
- __doc__ = _('Modify a permission.')
-
- msg_summary = _('Modified permission "%(value)s"')
- has_output_params = baseldap.LDAPUpdate.has_output_params + output_params
-
- def execute(self, *keys, **options):
- context.filter_ops = self.obj.preprocess_options(
- options, return_filter_ops=True)
- return super(permission_mod, self).execute(*keys, **options)
-
- def get_options(self):
- for opt in super(permission_mod, self).get_options():
- if opt.name == 'rename':
- yield _disallow_colon(opt)
- else:
- yield opt
-
- def pre_callback(self, ldap, dn, entry, attrs_list, *keys, **options):
- if 'rename' in options and not options['rename']:
- raise errors.ValidationError(name='rename',
- error='New name can not be empty')
-
- try:
- attrs_list = self.obj.default_attributes
- old_entry = ldap.get_entry(dn, attrs_list=attrs_list)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- self.obj.reject_system(old_entry)
- self.obj.upgrade_permission(old_entry)
-
- if 'MANAGED' in old_entry.get('ipapermissiontype', ()):
- for option_name in sorted(options):
- if option_name == 'rename':
- raise errors.ValidationError(
- name=option_name,
- error=_('cannot rename managed permissions'))
- option = self.options[option_name]
- allow_mod = 'allow_mod_for_managed_permission' in option.flags
- if (option.attribute and not allow_mod or
- option_name == 'extratargetfilter'):
- raise errors.ValidationError(
- name=option_name,
- error=_('not modifiable on managed permissions'))
- if context.filter_ops.get('add'):
- raise errors.ValidationError(
- name='ipapermtargetfilter',
- error=_('not modifiable on managed permissions'))
- else:
- if options.get('ipapermexcludedattr'):
- # prevent setting excluded attributes on normal permissions
- # (but do allow deleting them all)
- raise errors.ValidationError(
- name='ipapermexcludedattr',
- error=_('only available on managed permissions'))
-
- if 'attrs' in options:
- if any(a in options for a in ('ipapermincludedattr',
- 'ipapermexcludedattr')):
- raise errors.ValidationError(
- name='attrs',
- error=_('attrs and included/excluded attributes are '
- 'mutually exclusive'))
- attrs = set(options.pop('attrs') or ())
- defaults = set(old_entry.get('ipapermdefaultattr', ()))
- entry['ipapermincludedattr'] = list(attrs - defaults)
- entry['ipapermexcludedattr'] = list(defaults - attrs)
-
- # Check setting bindtype for an assigned permission
- if options.get('ipapermbindruletype') and old_entry.get('member'):
- raise errors.ValidationError(
- name='ipapermbindruletype',
- error=_('cannot set bindtype for a permission that is '
- 'assigned to a privilege'))
-
- # Since `entry` only contains the attributes we are currently changing,
- # it cannot be used directly to generate an ACI.
- # First we need to copy the original data into it.
- for key, value in old_entry.items():
- if (key not in options and
- key != 'cn' and
- key not in self.obj.attribute_members):
- entry.setdefault(key, value)
-
- # For extratargetfilter, add it to the implicit filters
- # to get the full target filter
- if 'extratargetfilter' in options:
- filter_attr_info = self.obj._get_filter_attr_info(entry)
- entry['ipapermtargetfilter'] = (
- list(options['extratargetfilter'] or []) +
- list(filter_attr_info['implicit_targetfilters']))
-
- filter_ops = context.filter_ops
- old_filter_attr_info = self.obj._get_filter_attr_info(old_entry)
- old_implicit_filters = old_filter_attr_info['implicit_targetfilters']
- removes = filter_ops.get('remove', [])
- new_filters = set(
- filt for filt in (entry.get('ipapermtargetfilter') or [])
- if filt not in old_implicit_filters or
- not any(rem.match(filt) for rem in removes))
- new_filters.update(filter_ops.get('add', []))
- new_filters.update(options.get('ipapermtargetfilter') or [])
- entry['ipapermtargetfilter'] = list(new_filters)
-
- if not entry.get('ipapermlocation'):
- entry['ipapermlocation'] = [self.api.env.basedn]
-
- self.obj.validate_permission(entry)
-
- old_location = old_entry.single_value.get('ipapermlocation',
- self.api.env.basedn)
- if old_location == options.get('ipapermlocation', old_location):
- context.permision_moving_aci = False
- else:
- context.permision_moving_aci = True
- try:
- context.old_aci_info = self.obj.remove_aci(old_entry)
- except errors.NotFound as e:
- self.log.error('permission ACI not found: %s' % e)
-
- # To pass data to postcallback, we currently need to use the context
- context.old_entry = old_entry
-
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- if call_func.__name__ == 'update_entry':
- self._revert_aci()
- raise exc
-
- def _revert_aci(self):
- old_aci_info = getattr(context, 'old_aci_info', None)
- if old_aci_info:
- # Try to roll back the old ACI
- entry, old_aci_string = old_aci_info
- if old_aci_string:
- self.log.warning('Reverting ACI on %s to %s' % (entry.dn,
- old_aci_string))
- entry['aci'].append(old_aci_string)
- self.Backend.ldap2.update_entry(entry)
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- old_entry = context.old_entry
-
- try:
- if context.permision_moving_aci:
- self.obj.add_aci(entry)
- else:
- self.obj.update_aci(entry, old_entry.single_value['cn'])
- except Exception:
- # Don't revert attribute which doesn't exist in LDAP
- entry.pop('attributelevelrights', None)
-
- self.log.error('Error updating ACI: %s' % traceback.format_exc())
- self.log.warning('Reverting entry')
- old_entry.reset_modlist(entry)
- ldap.update_entry(old_entry)
- self._revert_aci()
- raise
- self.obj.postprocess_result(entry, options)
- entry['dn'] = entry.dn
- return dn
-
-
-@register()
-class permission_find(baseldap.LDAPSearch):
- __doc__ = _('Search for permissions.')
-
- msg_summary = ngettext(
- '%(count)d permission matched', '%(count)d permissions matched', 0)
- has_output_params = baseldap.LDAPSearch.has_output_params + output_params
-
- def execute(self, *keys, **options):
- self.obj.preprocess_options(options, merge_targetfilter=True)
- return super(permission_find, self).execute(*keys, **options)
-
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope,
- *args, **options):
- if 'attrs' in options and 'ipapermincludedattr' in options:
- raise errors.ValidationError(
- name='attrs',
- error=_('attrs and included/excluded attributes are '
- 'mutually exclusive'))
-
- if options.get('attrs'):
- # Effective attributes:
- # each attr must be in either default or included,
- # but not in excluded
- filters = ldap.combine_filters(
- [filters] + [
- '(&'
- '(|'
- '(ipapermdefaultattr=%(attr)s)'
- '(ipapermincludedattr=%(attr)s))'
- '(!(ipapermexcludedattr=%(attr)s)))' % {'attr': attr}
- for attr in options['attrs']
- ],
- ldap.MATCH_ALL,
- )
-
- return filters, base_dn, scope
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if 'attrs' in options:
- options['ipapermincludedattr'] = options['attrs']
-
- attribute_options = [o for o in options
- if (o in self.options and
- self.options[o].attribute)]
-
- if not options.get('pkey_only'):
- for entry in entries:
- # Old-style permissions might have matched (e.g. by name)
- self.obj.upgrade_permission(entry, output_only=True)
-
- if not truncated:
- if 'sizelimit' in options:
- max_entries = options['sizelimit']
- else:
- max_entries = self.api.Backend.ldap2.size_limit
-
- filters = ['(objectclass=ipaPermission)',
- '(!(ipaPermissionType=V2))']
- if 'name' in options:
- filters.append(ldap.make_filter_from_attr('cn',
- options['name'],
- exact=False))
- attrs_list = list(self.obj.default_attributes)
- attrs_list += list(self.obj.attribute_members)
- if options.get('all'):
- attrs_list.append('*')
- try:
- legacy_entries = ldap.get_entries(
- base_dn=DN(self.obj.container_dn, self.api.env.basedn),
- filter=ldap.combine_filters(filters, rules=ldap.MATCH_ALL),
- attrs_list=attrs_list)
- # Retrieve the root entry (with all legacy ACIs) at once
- root_entry = ldap.get_entry(DN(api.env.basedn), ['aci'])
- except errors.NotFound:
- legacy_entries = ()
- cached_root_entry = None
- self.log.debug('potential legacy entries: %s', len(legacy_entries))
- nonlegacy_names = {e.single_value['cn'] for e in entries}
- for entry in legacy_entries:
- if entry.single_value['cn'] in nonlegacy_names:
- continue
- if max_entries > 0 and len(entries) > max_entries:
- # We've over the limit, pop the last entry and set
- # truncated flag
- # (this is easier to do than checking before adding
- # the entry to results)
- # (max_entries <= 0 means unlimited)
- entries.pop()
- truncated = True
- break
- self.obj.upgrade_permission(entry, output_only=True,
- cached_acientry=root_entry)
- # If all given options match, include the entry
- # Do a case-insensitive match, on any value if multi-valued
- for opt in attribute_options:
- optval = options[opt]
- if not isinstance(optval, (tuple, list)):
- optval = [optval]
- value = entry.get(opt)
- if not value:
- break
- if not all(any(str(ov).lower() in str(v).lower()
- for v in value) for ov in optval):
- break
- else:
- # Each search term must be present in some
- # attribute value
- for arg in args:
- if arg:
- arg = arg.lower()
- if not any(arg in str(value).lower()
- for values in entry.values()
- for value in values):
- break
- else:
- entries.append(entry)
-
- for entry in entries:
- if options.get('pkey_only'):
- for opt_name in list(entry):
- if opt_name != self.obj.primary_key.name:
- del entry[opt_name]
- else:
- self.obj.postprocess_result(entry, options)
-
- return truncated
-
-
-@register()
-class permission_show(baseldap.LDAPRetrieve):
- __doc__ = _('Display information about a permission.')
- has_output_params = baseldap.LDAPRetrieve.has_output_params + output_params
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- self.obj.upgrade_permission(entry, output_only=True)
- self.obj.postprocess_result(entry, options)
- return dn
-
-
-@register()
-class permission_add_member(baseldap.LDAPAddMember):
- """Add members to a permission."""
- NO_CLI = True
-
- def pre_callback(self, ldap, dn, member_dns, failed, *keys, **options):
- # We can only add permissions with bind rule type set to
- # "permission" (or old-style permissions)
- validate_permission_to_privilege(self.api, keys[-1])
- return dn
-
-
-@register()
-class permission_remove_member(baseldap.LDAPRemoveMember):
- """Remove members from a permission."""
- NO_CLI = True
diff --git a/ipalib/plugins/ping.py b/ipalib/plugins/ping.py
deleted file mode 100644
index 6a514125c..000000000
--- a/ipalib/plugins/ping.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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 ipalib import Command
-from ipalib import output
-from ipalib import _
-from ipalib.plugable import Registry
-from ipapython.version import VERSION, API_VERSION
-
-__doc__ = _("""
-Ping the remote IPA server to ensure it is running.
-
-The ping command sends an echo request to an IPA server. The server
-returns its version information. This is used by an IPA client
-to confirm that the server is available and accepting requests.
-
-The server from xmlrpc_uri in /etc/ipa/default.conf is contacted first.
-If it does not respond then the client will contact any servers defined
-by ldap SRV records in DNS.
-
-EXAMPLES:
-
- Ping an IPA server:
- ipa ping
- ------------------------------------------
- IPA server version 2.1.9. API version 2.20
- ------------------------------------------
-
- Ping an IPA server verbosely:
- ipa -v ping
- ipa: INFO: trying https://ipa.example.com/ipa/xml
- ipa: INFO: Forwarding 'ping' to server 'https://ipa.example.com/ipa/xml'
- -----------------------------------------------------
- IPA server version 2.1.9. API version 2.20
- -----------------------------------------------------
-""")
-
-register = Registry()
-
-
-@register()
-class ping(Command):
- __doc__ = _('Ping a remote server.')
-
- has_output = (
- output.summary,
- )
-
- def execute(self, **options):
- """
- A possible enhancement would be to take an argument and echo it
- back but a fixed value works for now.
- """
- return dict(summary=u'IPA server version %s. API version %s' % (VERSION, API_VERSION))
diff --git a/ipalib/plugins/pkinit.py b/ipalib/plugins/pkinit.py
deleted file mode 100644
index 9aa101063..000000000
--- a/ipalib/plugins/pkinit.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Authors:
-# Simo Sorce <ssorce@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 ipalib import api, errors
-from ipalib import Str
-from ipalib import Object, Command
-from ipalib import _
-from ipalib.plugable import Registry
-from ipapython.dn import DN
-
-__doc__ = _("""
-Kerberos pkinit options
-
-Enable or disable anonymous pkinit using the principal
-WELLKNOWN/ANONYMOUS@REALM. The server must have been installed with
-pkinit support.
-
-EXAMPLES:
-
- Enable anonymous pkinit:
- ipa pkinit-anonymous enable
-
- Disable anonymous pkinit:
- ipa pkinit-anonymous disable
-
-For more information on anonymous pkinit see:
-
-http://k5wiki.kerberos.org/wiki/Projects/Anonymous_pkinit
-""")
-
-register = Registry()
-
-@register()
-class pkinit(Object):
- """
- PKINIT Options
- """
- object_name = _('pkinit')
-
- label=_('PKINIT')
-
-
-def valid_arg(ugettext, action):
- """
- Accepts only Enable/Disable.
- """
- a = action.lower()
- if a != 'enable' and a != 'disable':
- raise errors.ValidationError(
- name='action',
- error=_('Unknown command %s') % action
- )
-
-@register()
-class pkinit_anonymous(Command):
- __doc__ = _('Enable or Disable Anonymous PKINIT.')
-
- princ_name = 'WELLKNOWN/ANONYMOUS@%s' % api.env.realm
- default_dn = DN(('krbprincipalname', princ_name), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn)
-
- takes_args = (
- Str('action', valid_arg),
- )
-
- def execute(self, action, **options):
- ldap = self.api.Backend.ldap2
- set_lock = False
- lock = None
-
- entry_attrs = ldap.get_entry(self.default_dn, ['nsaccountlock'])
-
- if 'nsaccountlock' in entry_attrs:
- lock = entry_attrs['nsaccountlock'][0].lower()
-
- if action.lower() == 'enable':
- if lock == 'true':
- set_lock = True
- lock = None
- elif action.lower() == 'disable':
- if lock != 'true':
- set_lock = True
- lock = 'TRUE'
-
- if set_lock:
- entry_attrs['nsaccountlock'] = lock
- ldap.update_entry(entry_attrs)
-
- return dict(result=True)
-
diff --git a/ipalib/plugins/privilege.py b/ipalib/plugins/privilege.py
deleted file mode 100644
index b46807c3f..000000000
--- a/ipalib/plugins/privilege.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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 .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember,
- LDAPAddReverseMember,
- LDAPRemoveReverseMember)
-from ipalib import api, _, ngettext, errors
-from ipalib.plugable import Registry
-from ipalib import Str
-from ipalib import output
-from ipapython.dn import DN
-
-__doc__ = _("""
-Privileges
-
-A privilege combines permissions into a logical task. A permission provides
-the rights to do a single task. There are some IPA operations that require
-multiple permissions to succeed. A privilege is where permissions are
-combined in order to perform a specific task.
-
-For example, adding a user requires the following permissions:
- * Creating a new user entry
- * Resetting a user password
- * Adding the new user to the default IPA users group
-
-Combining these three low-level tasks into a higher level task in the
-form of a privilege named "Add User" makes it easier to manage Roles.
-
-A privilege may not contain other privileges.
-
-See role and permission for additional information.
-""")
-
-register = Registry()
-
-
-def validate_permission_to_privilege(api, permission):
- ldap = api.Backend.ldap2
- ldapfilter = ldap.combine_filters(rules='&', filters=[
- '(objectClass=ipaPermissionV2)', '(!(ipaPermBindRuleType=permission))',
- ldap.make_filter_from_attr('cn', permission, rules='|')])
- try:
- entries, truncated = ldap.find_entries(
- filter=ldapfilter,
- attrs_list=['cn', 'ipapermbindruletype'],
- base_dn=DN(api.env.container_permission, api.env.basedn),
- size_limit=1)
- except errors.NotFound:
- pass
- else:
- entry = entries[0]
- message = _('cannot add permission "%(perm)s" with bindtype '
- '"%(bindtype)s" to a privilege')
- raise errors.ValidationError(
- name='permission',
- error=message % {
- 'perm': entry.single_value['cn'],
- 'bindtype': entry.single_value.get(
- 'ipapermbindruletype', 'permission')})
-
-
-@register()
-class privilege(LDAPObject):
- """
- Privilege object.
- """
- container_dn = api.env.container_privilege
- object_name = _('privilege')
- object_name_plural = _('privileges')
- object_class = ['nestedgroup', 'groupofnames']
- permission_filter_objectclasses = ['groupofnames']
- default_attributes = ['cn', 'description', 'member', 'memberof']
- attribute_members = {
- 'member': ['role'],
- 'memberof': ['permission'],
- }
- reverse_members = {
- 'member': ['permission'],
- }
- rdn_is_primary_key = True
- managed_permissions = {
- 'System: Read Privileges': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'member', 'memberof',
- 'o', 'objectclass', 'ou', 'owner', 'seealso', 'memberuser',
- 'memberhost',
- },
- 'default_privileges': {'RBAC Readers'},
- },
- 'System: Add Privileges': {
- 'ipapermright': {'add'},
- 'default_privileges': {'Delegation Administrator'},
- },
- 'System: Modify Privileges': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'o', 'ou', 'owner',
- 'seealso',
- },
- 'default_privileges': {'Delegation Administrator'},
- },
- 'System: Remove Privileges': {
- 'ipapermright': {'delete'},
- 'default_privileges': {'Delegation Administrator'},
- },
- }
-
- label = _('Privileges')
- label_singular = _('Privilege')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Privilege name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Privilege description'),
- ),
- )
-
-
-@register()
-class privilege_add(LDAPCreate):
- __doc__ = _('Add a new privilege.')
-
- msg_summary = _('Added privilege "%(value)s"')
-
-
-@register()
-class privilege_del(LDAPDelete):
- __doc__ = _('Delete a privilege.')
-
- msg_summary = _('Deleted privilege "%(value)s"')
-
-
-@register()
-class privilege_mod(LDAPUpdate):
- __doc__ = _('Modify a privilege.')
-
- msg_summary = _('Modified privilege "%(value)s"')
-
-
-@register()
-class privilege_find(LDAPSearch):
- __doc__ = _('Search for privileges.')
-
- msg_summary = ngettext(
- '%(count)d privilege matched', '%(count)d privileges matched', 0
- )
-
-
-@register()
-class privilege_show(LDAPRetrieve):
- __doc__ = _('Display information about a privilege.')
-
-
-@register()
-class privilege_add_member(LDAPAddMember):
- __doc__ = _('Add members to a privilege.')
-
- NO_CLI=True
-
-
-@register()
-class privilege_remove_member(LDAPRemoveMember):
- """
- Remove members from a privilege
- """
- NO_CLI=True
-
-
-@register()
-class privilege_add_permission(LDAPAddReverseMember):
- __doc__ = _('Add permissions to a privilege.')
-
- show_command = 'privilege_show'
- member_command = 'permission_add_member'
- reverse_attr = 'permission'
- member_attr = 'privilege'
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of permissions added'),
- ),
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- if options.get('permission'):
- # We can only add permissions with bind rule type set to
- # "permission" (or old-style permissions)
- validate_permission_to_privilege(self.api, options['permission'])
- return dn
-
-
-@register()
-class privilege_remove_permission(LDAPRemoveReverseMember):
- __doc__ = _('Remove permissions from a privilege.')
-
- show_command = 'privilege_show'
- member_command = 'permission_remove_member'
- reverse_attr = 'permission'
- member_attr = 'privilege'
-
- permission_count_out = ('%i permission removed.', '%i permissions removed.')
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of permissions removed'),
- ),
- )
diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py
deleted file mode 100644
index 5a2202aa0..000000000
--- a/ipalib/plugins/pwpolicy.py
+++ /dev/null
@@ -1,611 +0,0 @@
-# Authors:
-# Pavel Zuna <pzuna@redhat.com>
-# Martin Kosek <mkosek@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 ipalib import api
-from ipalib import Int, Str, DNParam
-from ipalib import errors
-from .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPRetrieve,
- LDAPSearch)
-from ipalib import _
-from ipalib.plugable import Registry
-from ipalib.request import context
-from ipapython.ipautil import run
-from ipapython.dn import DN
-from distutils import version
-
-import six
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Password policy
-
-A password policy sets limitations on IPA passwords, including maximum
-lifetime, minimum lifetime, the number of passwords to save in
-history, the number of character classes required (for stronger passwords)
-and the minimum password length.
-
-By default there is a single, global policy for all users. You can also
-create a password policy to apply to a group. Each user is only subject
-to one password policy, either the group policy or the global policy. A
-group policy stands alone; it is not a super-set of the global policy plus
-custom settings.
-
-Each group password policy requires a unique priority setting. If a user
-is in multiple groups that have password policies, this priority determines
-which password policy is applied. A lower value indicates a higher priority
-policy.
-
-Group password policies are automatically removed when the groups they
-are associated with are removed.
-
-EXAMPLES:
-
- Modify the global policy:
- ipa pwpolicy-mod --minlength=10
-
- Add a new group password policy:
- ipa pwpolicy-add --maxlife=90 --minlife=1 --history=10 --minclasses=3 --minlength=8 --priority=10 localadmins
-
- Display the global password policy:
- ipa pwpolicy-show
-
- Display a group password policy:
- ipa pwpolicy-show localadmins
-
- Display the policy that would be applied to a given user:
- ipa pwpolicy-show --user=tuser1
-
- Modify a group password policy:
- ipa pwpolicy-mod --minclasses=2 localadmins
-""")
-
-register = Registry()
-
-@register()
-class cosentry(LDAPObject):
- """
- Class of Service object used for linking policies with groups
- """
- NO_CLI = True
-
- container_dn = DN(('cn', 'costemplates'), api.env.container_accounts)
- object_class = ['top', 'costemplate', 'extensibleobject', 'krbcontainer']
- permission_filter_objectclasses = ['costemplate']
- default_attributes = ['cn', 'cospriority', 'krbpwdpolicyreference']
- managed_permissions = {
- 'System: Read Group Password Policy costemplate': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'cospriority', 'krbpwdpolicyreference', 'objectclass',
- },
- 'default_privileges': {
- 'Password Policy Readers',
- 'Password Policy Administrator',
- },
- },
- 'System: Add Group Password Policy costemplate': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Group Password Policy costemplate";allow (add) groupdn = "ldap:///cn=Add Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- 'System: Delete Group Password Policy costemplate': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Delete Group Password Policy costemplate";allow (delete) groupdn = "ldap:///cn=Delete Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- 'System: Modify Group Password Policy costemplate': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'cospriority'},
- 'replaces': [
- '(targetattr = "cospriority")(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy costemplate";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- }
-
- takes_params = (
- Str('cn', primary_key=True),
- DNParam('krbpwdpolicyreference'),
- Int('cospriority', minvalue=0),
- )
-
- priority_not_unique_msg = _(
- 'priority must be a unique value (%(prio)d already used by %(gname)s)'
- )
-
- def get_dn(self, *keys, **options):
- group_dn = self.api.Object.group.get_dn(keys[-1])
- return self.backend.make_dn_from_attr(
- 'cn', group_dn, DN(self.container_dn, api.env.basedn)
- )
-
- def check_priority_uniqueness(self, *keys, **options):
- if options.get('cospriority') is not None:
- entries = self.methods.find(
- cospriority=options['cospriority']
- )['result']
- if len(entries) > 0:
- group_name = self.api.Object.group.get_primary_key_from_dn(
- DN(entries[0]['cn'][0]))
- raise errors.ValidationError(
- name='priority',
- error=self.priority_not_unique_msg % {
- 'prio': options['cospriority'],
- 'gname': group_name,
- }
- )
-
-
-@register()
-class cosentry_add(LDAPCreate):
- NO_CLI = True
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- # check for existence of the group
- group_dn = self.api.Object.group.get_dn(keys[-1])
- try:
- result = ldap.get_entry(group_dn, ['objectclass'])
- except errors.NotFound:
- self.api.Object.group.handle_not_found(keys[-1])
-
- oc = [x.lower() for x in result['objectclass']]
- if 'mepmanagedentry' in oc:
- raise errors.ManagedPolicyError()
- self.obj.check_priority_uniqueness(*keys, **options)
- del entry_attrs['cn']
- return dn
-
-
-@register()
-class cosentry_del(LDAPDelete):
- NO_CLI = True
-
-
-@register()
-class cosentry_mod(LDAPUpdate):
- NO_CLI = True
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- new_cospriority = options.get('cospriority')
- if new_cospriority is not None:
- cos_entry = self.api.Command.cosentry_show(keys[-1])['result']
- old_cospriority = int(cos_entry['cospriority'][0])
-
- # check uniqueness only when the new priority differs
- if old_cospriority != new_cospriority:
- self.obj.check_priority_uniqueness(*keys, **options)
- return dn
-
-
-@register()
-class cosentry_show(LDAPRetrieve):
- NO_CLI = True
-
-
-@register()
-class cosentry_find(LDAPSearch):
- NO_CLI = True
-
-
-global_policy_name = 'global_policy'
-global_policy_dn = DN(('cn', global_policy_name), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn)
-
-@register()
-class pwpolicy(LDAPObject):
- """
- Password Policy object
- """
- container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos'))
- object_name = _('password policy')
- object_name_plural = _('password policies')
- object_class = ['top', 'nscontainer', 'krbpwdpolicy']
- permission_filter_objectclasses = ['krbpwdpolicy']
- default_attributes = [
- 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife',
- 'krbpwdhistorylength', 'krbpwdmindiffchars', 'krbpwdminlength',
- 'krbpwdmaxfailure', 'krbpwdfailurecountinterval',
- 'krbpwdlockoutduration',
- ]
- managed_permissions = {
- 'System: Read Group Password Policy': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife',
- 'krbpwdfailurecountinterval', 'krbpwdhistorylength',
- 'krbpwdlockoutduration', 'krbpwdmaxfailure',
- 'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass',
- },
- 'default_privileges': {
- 'Password Policy Readers',
- 'Password Policy Administrator',
- },
- },
- 'System: Add Group Password Policy': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Add Group Password Policy";allow (add) groupdn = "ldap:///cn=Add Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- 'System: Delete Group Password Policy': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=Delete Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- 'System: Modify Group Password Policy': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval',
- 'krbpwdhistorylength', 'krbpwdlockoutduration',
- 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength'
- },
- 'replaces': [
- '(targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdhistorylength || krbpwdmindiffchars || krbpwdminlength || krbpwdmaxfailure || krbpwdfailurecountinterval || krbpwdlockoutduration")(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Password Policy Administrator'},
- },
- }
-
- MIN_KRB5KDC_WITH_LOCKOUT = "1.8"
- has_lockout = False
- lockout_params = ()
-
- result = run(['klist', '-V'], raiseonerr=False, capture_output=True)
- if result.returncode == 0:
- verstr = result.output.split()[-1]
- ver = version.LooseVersion(verstr)
- min = version.LooseVersion(MIN_KRB5KDC_WITH_LOCKOUT)
- if ver >= min:
- has_lockout = True
-
- if has_lockout:
- lockout_params = (
- Int('krbpwdmaxfailure?',
- cli_name='maxfail',
- label=_('Max failures'),
- doc=_('Consecutive failures before lockout'),
- minvalue=0,
- ),
- Int('krbpwdfailurecountinterval?',
- cli_name='failinterval',
- label=_('Failure reset interval'),
- doc=_('Period after which failure count will be reset (seconds)'),
- minvalue=0,
- ),
- Int('krbpwdlockoutduration?',
- cli_name='lockouttime',
- label=_('Lockout duration'),
- doc=_('Period for which lockout is enforced (seconds)'),
- minvalue=0,
- ),
- )
-
- label = _('Password Policies')
- label_singular = _('Password Policy')
-
- takes_params = (
- Str('cn?',
- cli_name='group',
- label=_('Group'),
- doc=_('Manage password policy for specific group'),
- primary_key=True,
- ),
- Int('krbmaxpwdlife?',
- cli_name='maxlife',
- label=_('Max lifetime (days)'),
- doc=_('Maximum password lifetime (in days)'),
- minvalue=0,
- maxvalue=20000, # a little over 54 years
- ),
- Int('krbminpwdlife?',
- cli_name='minlife',
- label=_('Min lifetime (hours)'),
- doc=_('Minimum password lifetime (in hours)'),
- minvalue=0,
- ),
- Int('krbpwdhistorylength?',
- cli_name='history',
- label=_('History size'),
- doc=_('Password history size'),
- minvalue=0,
- ),
- Int('krbpwdmindiffchars?',
- cli_name='minclasses',
- label=_('Character classes'),
- doc=_('Minimum number of character classes'),
- minvalue=0,
- maxvalue=5,
- ),
- Int('krbpwdminlength?',
- cli_name='minlength',
- label=_('Min length'),
- doc=_('Minimum length of password'),
- minvalue=0,
- ),
- Int('cospriority',
- cli_name='priority',
- label=_('Priority'),
- doc=_('Priority of the policy (higher number means lower priority'),
- minvalue=0,
- flags=('virtual_attribute',),
- ),
- ) + lockout_params
-
- def get_dn(self, *keys, **options):
- if keys[-1] is not None:
- return self.backend.make_dn_from_attr(
- self.primary_key.name, keys[-1],
- DN(self.container_dn, api.env.basedn)
- )
- return global_policy_dn
-
- def convert_time_for_output(self, entry_attrs, **options):
- # Convert seconds to hours and days for displaying to user
- if not options.get('raw', False):
- if 'krbmaxpwdlife' in entry_attrs:
- entry_attrs['krbmaxpwdlife'][0] = unicode(
- int(entry_attrs['krbmaxpwdlife'][0]) // 86400
- )
- if 'krbminpwdlife' in entry_attrs:
- entry_attrs['krbminpwdlife'][0] = unicode(
- int(entry_attrs['krbminpwdlife'][0]) // 3600
- )
-
- def convert_time_on_input(self, entry_attrs):
- # Convert hours and days to seconds for writing to LDAP
- if 'krbmaxpwdlife' in entry_attrs and entry_attrs['krbmaxpwdlife']:
- entry_attrs['krbmaxpwdlife'] = entry_attrs['krbmaxpwdlife'] * 86400
- if 'krbminpwdlife' in entry_attrs and entry_attrs['krbminpwdlife']:
- entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600
-
- def validate_lifetime(self, entry_attrs, add=False, *keys):
- """
- Ensure that the maximum lifetime is greater than the minimum.
- If there is no minimum lifetime set then don't return an error.
- """
- maxlife=entry_attrs.get('krbmaxpwdlife', None)
- minlife=entry_attrs.get('krbminpwdlife', None)
- existing_entry = {}
- if not add: # then read existing entry
- existing_entry = self.api.Command.pwpolicy_show(keys[-1],
- all=True,
- )['result']
- if minlife is None and 'krbminpwdlife' in existing_entry:
- minlife = int(existing_entry['krbminpwdlife'][0]) * 3600
- if maxlife is None and 'krbmaxpwdlife' in existing_entry:
- maxlife = int(existing_entry['krbmaxpwdlife'][0]) * 86400
-
- if maxlife is not None and minlife is not None:
- if minlife > maxlife:
- raise errors.ValidationError(
- name='maxlife',
- error=_('Maximum password life must be greater than minimum.'),
- )
-
- def add_cospriority(self, entry, pwpolicy_name, rights=True):
- if pwpolicy_name and pwpolicy_name != global_policy_name:
- cos_entry = self.api.Command.cosentry_show(
- pwpolicy_name,
- rights=rights, all=rights
- )['result']
- if cos_entry.get('cospriority') is not None:
- entry['cospriority'] = cos_entry['cospriority']
- if rights:
- entry['attributelevelrights']['cospriority'] = \
- cos_entry['attributelevelrights']['cospriority']
-
-
-@register()
-class pwpolicy_add(LDAPCreate):
- __doc__ = _('Add a new group password policy.')
-
- def get_args(self):
- yield self.obj.primary_key.clone(attribute=True, required=True)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.convert_time_on_input(entry_attrs)
- self.obj.validate_lifetime(entry_attrs, True)
- self.api.Command.cosentry_add(
- keys[-1], krbpwdpolicyreference=dn,
- cospriority=options.get('cospriority')
- )
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.log.info('%r' % entry_attrs)
- # attribute rights are not allowed for pwpolicy_add
- self.obj.add_cospriority(entry_attrs, keys[-1], rights=False)
- self.obj.convert_time_for_output(entry_attrs, **options)
- return dn
-
-
-@register()
-class pwpolicy_del(LDAPDelete):
- __doc__ = _('Delete a group password policy.')
-
- def get_args(self):
- yield self.obj.primary_key.clone(
- attribute=True, required=True, multivalue=True
- )
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- if dn == global_policy_dn:
- raise errors.ValidationError(
- name='group',
- error=_('cannot delete global password policy')
- )
- return dn
-
- def post_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- try:
- self.api.Command.cosentry_del(keys[-1])
- except errors.NotFound:
- pass
- return True
-
-
-@register()
-class pwpolicy_mod(LDAPUpdate):
- __doc__ = _('Modify a group password policy.')
-
- def execute(self, cn=None, **options):
- return super(pwpolicy_mod, self).execute(cn, **options)
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.convert_time_on_input(entry_attrs)
- self.obj.validate_lifetime(entry_attrs, False, *keys)
- setattr(context, 'cosupdate', False)
- if options.get('cospriority') is not None:
- if keys[-1] is None:
- raise errors.ValidationError(
- name='priority',
- error=_('priority cannot be set on global policy')
- )
- try:
- self.api.Command.cosentry_mod(
- keys[-1], cospriority=options['cospriority']
- )
- except errors.EmptyModlist as e:
- if len(entry_attrs) == 1: # cospriority only was passed
- raise e
- else:
- setattr(context, 'cosupdate', True)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- rights = options.get('all', False) and options.get('rights', False)
- self.obj.add_cospriority(entry_attrs, keys[-1], rights)
- self.obj.convert_time_for_output(entry_attrs, **options)
- return dn
-
- def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs):
- if call_func.__name__ == 'update_entry':
- if isinstance(exc, errors.EmptyModlist):
- entry_attrs = call_args[0]
- cosupdate = getattr(context, 'cosupdate')
- if not entry_attrs or cosupdate:
- return
- raise exc
-
-
-@register()
-class pwpolicy_show(LDAPRetrieve):
- __doc__ = _('Display information about password policy.')
-
- takes_options = LDAPRetrieve.takes_options + (
- Str('user?',
- label=_('User'),
- doc=_('Display effective policy for a specific user'),
- ),
- )
-
- def execute(self, cn=None, **options):
- return super(pwpolicy_show, self).execute(cn, **options)
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- if options.get('user') is not None:
- user_entry = self.api.Command.user_show(
- options['user'], all=True
- )['result']
- if 'krbpwdpolicyreference' in user_entry:
- return user_entry.get('krbpwdpolicyreference', [dn])[0]
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- rights = options.get('all', False) and options.get('rights', False)
- self.obj.add_cospriority(entry_attrs, keys[-1], rights)
- self.obj.convert_time_for_output(entry_attrs, **options)
- return dn
-
-
-@register()
-class pwpolicy_find(LDAPSearch):
- __doc__ = _('Search for group password policies.')
-
- # this command does custom sorting in post_callback
- sort_result_entries = False
-
- def priority_sort_key(self, entry):
- """Key for sorting password policies
-
- returns a pair: (is_global, priority)
- """
- # global policy will be always last in the output
- if entry['cn'][0] == global_policy_name:
- return True, 0
- else:
- # policies with higher priority (lower number) will be at the
- # beginning of the list
- try:
- cospriority = int(entry['cospriority'][0])
- except KeyError:
- # if cospriority is not present in the entry, rather return 0
- # than crash
- cospriority = 0
- return False, cospriority
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for e in entries:
- # When pkey_only flag is on, entries should contain only a cn.
- # Add a cospriority attribute that will be used for sorting.
- # Attribute rights are not allowed for pwpolicy_find.
- self.obj.add_cospriority(e, e['cn'][0], rights=False)
-
- self.obj.convert_time_for_output(e, **options)
-
- # do custom entry sorting by its cospriority
- entries.sort(key=self.priority_sort_key)
-
- if options.get('pkey_only', False):
- # remove cospriority that was used for sorting
- for e in entries:
- try:
- del e['cospriority']
- except KeyError:
- pass
-
- return truncated
diff --git a/ipalib/plugins/radiusproxy.py b/ipalib/plugins/radiusproxy.py
deleted file mode 100644
index 44d87b9ae..000000000
--- a/ipalib/plugins/radiusproxy.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# 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 .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve)
-from ipalib import api, Str, Int, Password, _, ngettext
-from ipalib import errors
-from ipalib.plugable import Registry
-from ipalib.util import validate_hostname, validate_ipaddr
-from ipalib.errors import ValidationError
-import re
-
-__doc__ = _("""
-RADIUS Proxy Servers
-""") + _("""
-Manage RADIUS Proxy Servers.
-""") + _("""
-IPA supports the use of an external RADIUS proxy server for krb5 OTP
-authentications. This permits a great deal of flexibility when
-integrating with third-party authentication services.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Add a new server:
- ipa radiusproxy-add MyRADIUS --server=radius.example.com:1812
-""") + _("""
- Find all servers whose entries include the string "example.com":
- ipa radiusproxy-find example.com
-""") + _("""
- Examine the configuration:
- ipa radiusproxy-show MyRADIUS
-""") + _("""
- Change the secret:
- ipa radiusproxy-mod MyRADIUS --secret
-""") + _("""
- Delete a configuration:
- ipa radiusproxy-del MyRADIUS
-""")
-
-register = Registry()
-
-LDAP_ATTRIBUTE = re.compile("^[a-zA-Z][a-zA-Z0-9-]*$")
-def validate_attributename(ugettext, attr):
- if not LDAP_ATTRIBUTE.match(attr):
- raise ValidationError(name="ipatokenusermapattribute",
- error=_('invalid attribute name'))
-
-def validate_radiusserver(ugettext, server):
- split = server.rsplit(':', 1)
- server = split[0]
- if len(split) == 2:
- try:
- port = int(split[1])
- if (port < 0 or port > 65535):
- raise ValueError()
- except ValueError:
- raise ValidationError(name="ipatokenradiusserver",
- error=_('invalid port number'))
-
- if validate_ipaddr(server):
- return
-
- try:
- validate_hostname(server, check_fqdn=True, allow_underscore=True)
- except ValueError as e:
- raise errors.ValidationError(name="ipatokenradiusserver",
- error=str(e))
-
-
-@register()
-class radiusproxy(LDAPObject):
- """
- RADIUS Server object.
- """
- container_dn = api.env.container_radiusproxy
- object_name = _('RADIUS proxy server')
- object_name_plural = _('RADIUS proxy servers')
- object_class = ['ipatokenradiusconfiguration']
- default_attributes = ['cn', 'description', 'ipatokenradiusserver',
- 'ipatokenradiustimeout', 'ipatokenradiusretries', 'ipatokenusermapattribute'
- ]
- search_attributes = ['cn', 'description', 'ipatokenradiusserver']
- rdn_is_primary_key = True
- label = _('RADIUS Servers')
- label_singular = _('RADIUS Server')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('RADIUS proxy server name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this RADIUS proxy server'),
- ),
- Str('ipatokenradiusserver+', validate_radiusserver,
- cli_name='server',
- label=_('Server'),
- doc=_('The hostname or IP (with or without port)'),
- ),
- Password('ipatokenradiussecret',
- cli_name='secret',
- label=_('Secret'),
- doc=_('The secret used to encrypt data'),
- confirm=True,
- flags=['no_option'],
- ),
- Int('ipatokenradiustimeout?',
- cli_name='timeout',
- label=_('Timeout'),
- doc=_('The total timeout across all retries (in seconds)'),
- minvalue=1,
- ),
- Int('ipatokenradiusretries?',
- cli_name='retries',
- label=_('Retries'),
- doc=_('The number of times to retry authentication'),
- minvalue=0,
- maxvalue=10,
- ),
- Str('ipatokenusermapattribute?', validate_attributename,
- cli_name='userattr',
- label=_('User attribute'),
- doc=_('The username attribute on the user object'),
- ),
- )
-
-@register()
-class radiusproxy_add(LDAPCreate):
- __doc__ = _('Add a new RADIUS proxy server.')
- msg_summary = _('Added RADIUS proxy server "%(value)s"')
-
-@register()
-class radiusproxy_del(LDAPDelete):
- __doc__ = _('Delete a RADIUS proxy server.')
- msg_summary = _('Deleted RADIUS proxy server "%(value)s"')
-
-@register()
-class radiusproxy_mod(LDAPUpdate):
- __doc__ = _('Modify a RADIUS proxy server.')
- msg_summary = _('Modified RADIUS proxy server "%(value)s"')
-
-@register()
-class radiusproxy_find(LDAPSearch):
- __doc__ = _('Search for RADIUS proxy servers.')
- msg_summary = ngettext(
- '%(count)d RADIUS proxy server matched', '%(count)d RADIUS proxy servers matched', 0
- )
-
-@register()
-class radiusproxy_show(LDAPRetrieve):
- __doc__ = _('Display information about a RADIUS proxy server.')
diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py
deleted file mode 100644
index 3f8561091..000000000
--- a/ipalib/plugins/realmdomains.py
+++ /dev/null
@@ -1,340 +0,0 @@
-# Authors:
-# Ana Krivokapic <akrivoka@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/>.
-
-import six
-
-from ipalib import api, errors, messages
-from ipalib import Str, Flag
-from ipalib import _
-from ipalib.plugable import Registry
-from .baseldap import LDAPObject, LDAPUpdate, LDAPRetrieve
-from ipalib.util import has_soa_or_ns_record, validate_domain_name
-from ipalib.util import detect_dns_zone_realm_type
-from ipapython.dn import DN
-from ipapython.ipautil import get_domain_name
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Realm domains
-
-Manage the list of domains associated with IPA realm.
-
-EXAMPLES:
-
- Display the current list of realm domains:
- ipa realmdomains-show
-
- Replace the list of realm domains:
- ipa realmdomains-mod --domain=example.com
- ipa realmdomains-mod --domain={example1.com,example2.com,example3.com}
-
- Add a domain to the list of realm domains:
- ipa realmdomains-mod --add-domain=newdomain.com
-
- Delete a domain from the list of realm domains:
- ipa realmdomains-mod --del-domain=olddomain.com
-""")
-
-register = Registry()
-
-def _domain_name_normalizer(d):
- return d.lower().rstrip('.')
-
-def _domain_name_validator(ugettext, value):
- try:
- validate_domain_name(value, allow_slash=False)
- except ValueError as e:
- return unicode(e)
-
-
-@register()
-class realmdomains(LDAPObject):
- """
- List of domains associated with IPA realm.
- """
- container_dn = api.env.container_realm_domains
- permission_filter_objectclasses = ['domainrelatedobject']
- object_name = _('Realm domains')
- search_attributes = ['associateddomain']
- default_attributes = ['associateddomain']
- managed_permissions = {
- 'System: Read Realm Domains': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'associateddomain',
- },
- },
- 'System: Modify Realm Domains': {
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'associatedDomain',
- },
- 'default_privileges': {'DNS Administrators'},
- },
- }
-
- label = _('Realm Domains')
- label_singular = _('Realm Domains')
-
- takes_params = (
- Str('associateddomain+',
- _domain_name_validator,
- normalizer=_domain_name_normalizer,
- cli_name='domain',
- label=_('Domain'),
- ),
- Str('add_domain?',
- _domain_name_validator,
- normalizer=_domain_name_normalizer,
- cli_name='add_domain',
- label=_('Add domain'),
- ),
- Str('del_domain?',
- _domain_name_validator,
- normalizer=_domain_name_normalizer,
- cli_name='del_domain',
- label=_('Delete domain'),
- ),
- )
-
-
-
-@register()
-class realmdomains_mod(LDAPUpdate):
- __doc__ = _('Modify realm domains.')
-
- takes_options = LDAPUpdate.takes_options + (
- Flag('force',
- label=_('Force'),
- doc=_('Force adding domain even if not in DNS'),
- ),
- )
-
- def validate_domains(self, domains, force):
- """
- Validates the list of domains as candidates for additions to the
- realmdomains list.
-
- Requirements:
- - Each domain has SOA or NS record
- - Each domain belongs to the current realm
- """
-
- # Unless forced, check that each domain has SOA or NS records
- if not force:
- invalid_domains = [
- d for d in domains
- if not has_soa_or_ns_record(d)
- ]
-
- if invalid_domains:
- raise errors.ValidationError(
- name='domain',
- error= _(
- "DNS zone for each realmdomain must contain "
- "SOA or NS records. No records found for: %s"
- ) % ','.join(invalid_domains)
- )
-
- # Check realm alliegence for each domain
- domains_with_realm = [
- (domain, detect_dns_zone_realm_type(self.api, domain))
- for domain in domains
- ]
-
- foreign_domains = [
- domain for domain, realm in domains_with_realm
- if realm == 'foreign'
- ]
-
- unknown_domains = [
- domain for domain, realm in domains_with_realm
- if realm == 'unknown'
- ]
-
- # If there are any foreing realm domains, bail out
- if foreign_domains:
- raise errors.ValidationError(
- name='domain',
- error=_(
- 'The following domains do not belong '
- 'to this realm: %(domains)s'
- ) % dict(domains=','.join(foreign_domains))
- )
-
- # If there are any unknown domains, error out,
- # asking for _kerberos TXT records
-
- # Note: This can be forced, since realmdomains-mod
- # is called from dnszone-add where we know that
- # the domain being added belongs to our realm
- if not force and unknown_domains:
- raise errors.ValidationError(
- name='domain',
- error=_(
- 'The realm of the following domains could '
- 'not be detected: %(domains)s. If these are '
- 'domains that belong to the this realm, please '
- 'create a _kerberos TXT record containing "%(realm)s" '
- 'in each of them.'
- ) % dict(domains=','.join(unknown_domains),
- realm=self.api.env.realm)
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- associateddomain = entry_attrs.get('associateddomain')
- add_domain = entry_attrs.get('add_domain')
- del_domain = entry_attrs.get('del_domain')
- force = options.get('force')
-
- current_domain = get_domain_name()
-
- # User specified the list of domains explicitly
- if associateddomain:
- if add_domain or del_domain:
- raise errors.MutuallyExclusiveError(
- reason=_(
- "The --domain option cannot be used together "
- "with --add-domain or --del-domain. Use --domain "
- "to specify the whole realm domain list explicitly, "
- "to add/remove individual domains, use "
- "--add-domain/del-domain.")
- )
-
- # Make sure our domain is included in the list
- if current_domain not in associateddomain:
- raise errors.ValidationError(
- name='realmdomain list',
- error=_("IPA server domain cannot be omitted")
- )
-
- # Validate that each domain satisfies the requirements
- # for realmdomain
- self.validate_domains(domains=associateddomain, force=force)
-
- return dn
-
- # If --add-domain or --del-domain options were provided, read
- # the curent list from LDAP, modify it, and write the changes back
- domains = ldap.get_entry(dn)['associateddomain']
-
- if add_domain:
- self.validate_domains(domains=[add_domain], force=force)
- del entry_attrs['add_domain']
- domains.append(add_domain)
-
- if del_domain:
- if del_domain == current_domain:
- raise errors.ValidationError(
- name='del_domain',
- error=_("IPA server domain cannot be deleted")
- )
- del entry_attrs['del_domain']
-
- try:
- domains.remove(del_domain)
- except ValueError:
- raise errors.AttrValueNotFound(
- attr='associateddomain',
- value=del_domain
- )
-
- entry_attrs['associateddomain'] = domains
- return dn
-
- def execute(self, *keys, **options):
- dn = self.obj.get_dn(*keys, **options)
- ldap = self.obj.backend
-
- domains_old = set(ldap.get_entry(dn)['associateddomain'])
- result = super(realmdomains_mod, self).execute(*keys, **options)
- domains_new = set(ldap.get_entry(dn)['associateddomain'])
-
- domains_added = domains_new - domains_old
- domains_deleted = domains_old - domains_new
-
- # Add a _kerberos TXT record for zones that correspond with
- # domains which were added
- for domain in domains_added:
-
- # Skip our own domain
- if domain == api.env.domain:
- continue
-
- try:
- self.api.Command['dnsrecord_add'](
- unicode(domain),
- u'_kerberos',
- txtrecord=api.env.realm
- )
- except (errors.EmptyModlist, errors.NotFound,
- errors.ValidationError) as error:
-
- # If creation of the _kerberos TXT record failed, prompt
- # for manual intervention
- messages.add_message(
- options['version'],
- result,
- messages.KerberosTXTRecordCreationFailure(
- domain=domain,
- error=unicode(error),
- realm=self.api.env.realm
- )
- )
-
- # Delete _kerberos TXT record from zones that correspond with
- # domains which were deleted
- for domain in domains_deleted:
-
- # Skip our own domain
- if domain == api.env.domain:
- continue
-
- try:
- self.api.Command['dnsrecord_del'](
- unicode(domain),
- u'_kerberos',
- txtrecord=api.env.realm
- )
- except (errors.AttrValueNotFound, errors.NotFound,
- errors.ValidationError) as error:
- # If deletion of the _kerberos TXT record failed, prompt
- # for manual intervention
- messages.add_message(
- options['version'],
- result,
- messages.KerberosTXTRecordDeletionFailure(
- domain=domain, error=unicode(error)
- )
- )
-
- return result
-
-
-
-@register()
-class realmdomains_show(LDAPRetrieve):
- __doc__ = _('Display the list of realm domains.')
-
diff --git a/ipalib/plugins/role.py b/ipalib/plugins/role.py
deleted file mode 100644
index f4f0c98d9..000000000
--- a/ipalib/plugins/role.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-# 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 ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember,
- LDAPAddReverseMember,
- LDAPRemoveReverseMember)
-from ipalib import api, Str, _, ngettext
-from ipalib import output
-
-__doc__ = _("""
-Roles
-
-A role is used for fine-grained delegation. A permission grants the ability
-to perform given low-level tasks (add a user, modify a group, etc.). A
-privilege combines one or more permissions into a higher-level abstraction
-such as useradmin. A useradmin would be able to add, delete and modify users.
-
-Privileges are assigned to Roles.
-
-Users, groups, hosts and hostgroups may be members of a Role.
-
-Roles can not contain other roles.
-
-EXAMPLES:
-
- Add a new role:
- ipa role-add --desc="Junior-level admin" junioradmin
-
- Add some privileges to this role:
- ipa role-add-privilege --privileges=addusers junioradmin
- ipa role-add-privilege --privileges=change_password junioradmin
- ipa role-add-privilege --privileges=add_user_to_default_group junioradmin
-
- Add a group of users to this role:
- ipa group-add --desc="User admins" useradmins
- ipa role-add-member --groups=useradmins junioradmin
-
- Display information about a role:
- ipa role-show junioradmin
-
- The result of this is that any users in the group 'junioradmin' can
- add users, reset passwords or add a user to the default IPA user group.
-""")
-
-register = Registry()
-
-@register()
-class role(LDAPObject):
- """
- Role object.
- """
- container_dn = api.env.container_rolegroup
- object_name = _('role')
- object_name_plural = _('roles')
- object_class = ['groupofnames', 'nestedgroup']
- permission_filter_objectclasses = ['groupofnames']
- default_attributes = ['cn', 'description', 'member', 'memberof']
- # Role could have a lot of indirect members, but they are not in
- # attribute_members therefore they don't have to be in default_attributes
- # 'memberindirect', 'memberofindirect',
-
- attribute_members = {
- 'member': ['user', 'group', 'host', 'hostgroup', 'service'],
- 'memberof': ['privilege'],
- }
- reverse_members = {
- 'member': ['privilege'],
- }
- rdn_is_primary_key = True
- managed_permissions = {
- 'System: Read Roles': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'member', 'memberof',
- 'o', 'objectclass', 'ou', 'owner', 'seealso', 'memberuser',
- 'memberhost',
- },
- 'default_privileges': {'RBAC Readers'},
- },
- 'System: Add Roles': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=roles,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Roles";allow (add) groupdn = "ldap:///cn=Add Roles,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Delegation Administrator'},
- },
- 'System: Modify Role Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=roles,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Role membership";allow (write) groupdn = "ldap:///cn=Modify Role membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Delegation Administrator'},
- },
- 'System: Modify Roles': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'cn', 'description'},
- 'replaces': [
- '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=roles,cn=accounts,$SUFFIX")(version 3.0; acl "permission:Modify Roles";allow (write) groupdn = "ldap:///cn=Modify Roles,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Delegation Administrator'},
- },
- 'System: Remove Roles': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=roles,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Roles";allow (delete) groupdn = "ldap:///cn=Remove Roles,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Delegation Administrator'},
- },
- }
-
- label = _('Roles')
- label_singular = _('Role')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Role name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this role-group'),
- ),
- )
-
-
-
-@register()
-class role_add(LDAPCreate):
- __doc__ = _('Add a new role.')
-
- msg_summary = _('Added role "%(value)s"')
-
-
-
-@register()
-class role_del(LDAPDelete):
- __doc__ = _('Delete a role.')
-
- msg_summary = _('Deleted role "%(value)s"')
-
-
-
-@register()
-class role_mod(LDAPUpdate):
- __doc__ = _('Modify a role.')
-
- msg_summary = _('Modified role "%(value)s"')
-
-
-
-@register()
-class role_find(LDAPSearch):
- __doc__ = _('Search for roles.')
-
- msg_summary = ngettext(
- '%(count)d role matched', '%(count)d roles matched', 0
- )
-
-
-
-@register()
-class role_show(LDAPRetrieve):
- __doc__ = _('Display information about a role.')
-
-
-
-@register()
-class role_add_member(LDAPAddMember):
- __doc__ = _('Add members to a role.')
-
-
-
-@register()
-class role_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from a role.')
-
-
-
-@register()
-class role_add_privilege(LDAPAddReverseMember):
- __doc__ = _('Add privileges to a role.')
-
- show_command = 'role_show'
- member_command = 'privilege_add_member'
- reverse_attr = 'privilege'
- member_attr = 'role'
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of privileges added'),
- ),
- )
-
-
-
-@register()
-class role_remove_privilege(LDAPRemoveReverseMember):
- __doc__ = _('Remove privileges from a role.')
-
- show_command = 'role_show'
- member_command = 'privilege_remove_member'
- reverse_attr = 'privilege'
- member_attr = 'role'
-
- has_output = (
- output.Entry('result'),
- output.Output('failed',
- type=dict,
- doc=_('Members that could not be added'),
- ),
- output.Output('completed',
- type=int,
- doc=_('Number of privileges removed'),
- ),
- )
-
diff --git a/ipalib/plugins/schema.py b/ipalib/plugins/schema.py
deleted file mode 100644
index 8bc230350..000000000
--- a/ipalib/plugins/schema.py
+++ /dev/null
@@ -1,660 +0,0 @@
-#
-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
-#
-
-import importlib
-import itertools
-import sys
-
-import six
-
-from ipalib import errors
-from ipalib.crud import PKQuery, Retrieve, Search
-from ipalib.frontend import Command, Method, Object
-from ipalib.output import Entry, ListOfEntries, ListOfPrimaryKeys, PrimaryKey
-from ipalib.parameters import Any, Bool, Flag, Int, Str
-from ipalib.plugable import Registry
-from ipalib.text import _
-from ipapython.version import API_VERSION
-
-__doc__ = _("""
-API Schema
-""") + _("""
-Provides API introspection capabilities.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Show user-find details:
- ipa command-show user-find
-""") + _("""
- Find user-find parameters:
- ipa param-find user-find
-""")
-
-if six.PY3:
- unicode = str
-
-register = Registry()
-
-
-class BaseMetaObject(Object):
- takes_params = (
- Str(
- 'name',
- label=_("Name"),
- primary_key=True,
- normalizer=lambda name: name.replace(u'-', u'_'),
- flags={'no_search'},
- ),
- Str(
- 'doc?',
- label=_("Documentation"),
- flags={'no_search'},
- ),
- )
-
- def _get_obj(self, obj, **kwargs):
- raise NotImplementedError()
-
- def _retrieve(self, *args, **kwargs):
- raise NotImplementedError()
-
- def retrieve(self, *args, **kwargs):
- obj = self._retrieve(*args, **kwargs)
- obj = self._get_obj(obj, **kwargs)
- return obj
-
- def _search(self, *args, **kwargs):
- raise NotImplementedError()
-
- def _split_search_args(self, criteria=None):
- return [], criteria
-
- def search(self, *args, **kwargs):
- args, criteria = self._split_search_args(*args)
-
- result = self._search(*args, **kwargs)
- result = (self._get_obj(r, **kwargs) for r in result)
-
- if criteria:
- criteria = criteria.lower()
- result = (r for r in result
- if (criteria in r['name'].lower() or
- criteria in r.get('doc', u'').lower()))
-
- if not kwargs.get('all', False) and kwargs.get('pkey_only', False):
- result = ({'name': r['name']} for r in result)
-
- return result
-
-
-class BaseMetaRetrieve(Retrieve):
- def execute(self, *args, **options):
- obj = self.obj.retrieve(*args, **options)
- return dict(result=obj, value=args[-1])
-
-
-class BaseMetaSearch(Search):
- def get_options(self):
- for option in super(BaseMetaSearch, self).get_options():
- yield option
-
- yield Flag(
- 'pkey_only?',
- label=_("Primary key only"),
- doc=_("Results should contain primary key attribute only "
- "(\"%s\")") % 'name',
- )
-
- def execute(self, criteria=None, **options):
- result = list(self.obj.search(criteria, **options))
- return dict(result=result, count=len(result), truncated=False)
-
-
-class MetaObject(BaseMetaObject):
- takes_params = BaseMetaObject.takes_params + (
- Str(
- 'topic_topic?',
- label=_("Help topic"),
- flags={'no_search'},
- ),
- )
-
-
-class MetaRetrieve(BaseMetaRetrieve):
- pass
-
-
-class MetaSearch(BaseMetaSearch):
- pass
-
-
-@register()
-class command(MetaObject):
- takes_params = BaseMetaObject.takes_params + (
- Str(
- 'args_param*',
- label=_("Arguments"),
- flags={'no_search'},
- ),
- Str(
- 'options_param*',
- label=_("Options"),
- flags={'no_search'},
- ),
- Str(
- 'output_params_param*',
- label=_("Output parameters"),
- flags={'no_search'},
- ),
- Bool(
- 'no_cli?',
- label=_("Exclude from CLI"),
- flags={'no_search'},
- ),
- )
-
- def _get_obj(self, command, **kwargs):
- obj = dict()
- obj['name'] = unicode(command.name)
-
- if command.doc:
- obj['doc'] = unicode(command.doc)
-
- if command.topic:
- try:
- topic = self.api.Object.topic.retrieve(unicode(command.topic))
- except errors.NotFound:
- pass
- else:
- obj['topic_topic'] = topic['name']
-
- if command.NO_CLI:
- obj['no_cli'] = True
-
- if len(command.args):
- obj['args_param'] = tuple(unicode(n) for n in command.args)
-
- if len(command.options):
- obj['options_param'] = tuple(
- unicode(n) for n in command.options if n != 'version')
-
- if len(command.output_params):
- obj['output_params_param'] = tuple(
- unicode(n) for n in command.output_params
- if n not in command.params)
-
- return obj
-
- def _retrieve(self, name, **kwargs):
- try:
- return self.api.Command[name]
- except KeyError:
- raise errors.NotFound(
- reason=_("%(pkey)s: %(oname)s not found") % {
- 'pkey': name, 'oname': self.name,
- }
- )
-
- def _search(self, **kwargs):
- return self.api.Command()
-
-
-@register()
-class command_show(MetaRetrieve):
- __doc__ = _("Display information about a command.")
-
-
-@register()
-class command_find(MetaSearch):
- __doc__ = _("Search for commands.")
-
-
-@register()
-class command_defaults(PKQuery):
- NO_CLI = True
-
- takes_options = (
- Str('params*'),
- Any('kw?'),
- )
-
- def execute(self, name, **options):
- command = self.api.Command[name]
-
- params = options.get('params', [])
-
- kw = options.get('kw', {})
- if not isinstance(kw, dict):
- raise errors.ConversionError(name=name,
- error=_("must be a dictionary"))
-
- result = command.get_default(**kw)
- result = {n: v for n, v in result.items() if n in params}
-
- return dict(result=result)
-
-
-@register()
-class topic_(MetaObject):
- name = 'topic'
-
- def __init__(self, api):
- super(topic_, self).__init__(api)
- self.__topics = None
-
- def __get_topics(self):
- if self.__topics is None:
- topics = {}
- object.__setattr__(self, '_topic___topics', topics)
-
- for command in self.api.Command():
- topic_name = command.topic
-
- while topic_name is not None and topic_name not in topics:
- topic = topics[topic_name] = {'name': topic_name}
-
- for package in self.api.packages:
- module_name = '.'.join((package.__name__, topic_name))
- try:
- module = sys.modules[module_name]
- except KeyError:
- try:
- module = importlib.import_module(module_name)
- except ImportError:
- continue
-
- if module.__doc__ is not None:
- topic['doc'] = unicode(module.__doc__).strip()
-
- try:
- topic_name = module.topic
- except AttributeError:
- topic_name = None
- else:
- topic['topic_topic'] = topic_name
-
- return self.__topics
-
- def _get_obj(self, topic, **kwargs):
- return topic
-
- def _retrieve(self, name, **kwargs):
- try:
- return self.__get_topics()[name]
- except KeyError:
- raise errors.NotFound(
- reason=_("%(pkey)s: %(oname)s not found") % {
- 'pkey': name, 'oname': self.name,
- }
- )
-
- def _search(self, **kwargs):
- return self.__get_topics().values()
-
-
-@register()
-class topic_show(MetaRetrieve):
- __doc__ = _("Display information about a help topic.")
-
-
-@register()
-class topic_find(MetaSearch):
- __doc__ = _("Search for help topics.")
-
-
-class BaseParam(BaseMetaObject):
- takes_params = BaseMetaObject.takes_params + (
- Str(
- 'type?',
- label=_("Type"),
- flags={'no_search'},
- ),
- Bool(
- 'required?',
- label=_("Required"),
- flags={'no_search'},
- ),
- Bool(
- 'multivalue?',
- label=_("Multi-value"),
- flags={'no_search'},
- ),
- )
-
- def _split_search_args(self, commandname, criteria=None):
- return [commandname], criteria
-
-
-class BaseParamMethod(Method):
- def get_args(self):
- parent = self.api.Object.command
- parent_key = parent.primary_key
- yield parent_key.clone_rename(
- parent.name + parent_key.name,
- cli_name=parent.name,
- label=parent_key.label,
- required=True,
- query=True,
- )
-
- for arg in super(BaseParamMethod, self).get_args():
- yield arg
-
-
-class BaseParamRetrieve(BaseParamMethod, BaseMetaRetrieve):
- pass
-
-
-class BaseParamSearch(BaseParamMethod, BaseMetaSearch):
- pass
-
-
-@register()
-class param(BaseParam):
- takes_params = BaseParam.takes_params + (
- Bool(
- 'alwaysask?',
- label=_("Always ask"),
- flags={'no_search'},
- ),
- Bool(
- 'autofill?',
- label=_("Autofill"),
- flags={'no_search'},
- ),
- Str(
- 'cli_metavar?',
- label=_("CLI metavar"),
- flags={'no_search'},
- ),
- Str(
- 'cli_name?',
- label=_("CLI name"),
- flags={'no_search'},
- ),
- Bool(
- 'confirm',
- label=_("Confirm (password)"),
- flags={'no_search'},
- ),
- Str(
- 'default*',
- label=_("Default"),
- flags={'no_search'},
- ),
- Str(
- 'default_from_param*',
- label=_("Default from"),
- flags={'no_search'},
- ),
- Str(
- 'deprecated_cli_aliases*',
- label=_("Deprecated CLI aliases"),
- flags={'no_search'},
- ),
- Str(
- 'exclude*',
- label=_("Exclude from"),
- flags={'no_search'},
- ),
- Str(
- 'hint?',
- label=_("Hint"),
- flags={'no_search'},
- ),
- Str(
- 'include*',
- label=_("Include in"),
- flags={'no_search'},
- ),
- Str(
- 'label?',
- label=_("Label"),
- flags={'no_search'},
- ),
- Bool(
- 'no_convert?',
- label=_("Convert on server"),
- flags={'no_search'},
- ),
- Str(
- 'option_group?',
- label=_("Option group"),
- flags={'no_search'},
- ),
- Int(
- 'sortorder?',
- label=_("Sort order"),
- flags={'no_search'},
- ),
- Bool(
- 'dnsrecord_extra?',
- label=_("Extra field (DNS record)"),
- flags={'no_search'},
- ),
- Bool(
- 'dnsrecord_part?',
- label=_("Part (DNS record)"),
- flags={'no_search'},
- ),
- Bool(
- 'no_option?',
- label=_("No option"),
- flags={'no_search'},
- ),
- Bool(
- 'suppress_empty?',
- label=_("Suppress empty"),
- flags={'no_search'},
- ),
- Bool(
- 'sensitive?',
- label=_("Sensitive"),
- flags={'no_search'},
- ),
- )
-
- def _get_obj(self, param, **kwargs):
- obj = dict()
- obj['name'] = unicode(param.name)
-
- if param.type is unicode:
- obj['type'] = u'str'
- elif param.type is bytes:
- obj['type'] = u'bytes'
- elif param.type is not None:
- obj['type'] = unicode(param.type.__name__)
-
- if not param.required:
- obj['required'] = False
- if param.multivalue:
- obj['multivalue'] = True
- if param.password:
- obj['sensitive'] = True
-
- for key, value in param._Param__clonekw.items():
- if key in ('alwaysask',
- 'autofill',
- 'confirm',
- 'sortorder'):
- obj[key] = value
- elif key in ('cli_metavar',
- 'cli_name',
- 'doc',
- 'hint',
- 'label',
- 'option_group'):
- obj[key] = unicode(value)
- elif key == 'default':
- if param.multivalue:
- obj[key] = [unicode(v) for v in value]
- else:
- obj[key] = [unicode(value)]
- elif key == 'default_from':
- obj['default_from_param'] = list(unicode(k)
- for k in value.keys)
- elif key in ('deprecated_cli_aliases',
- 'exclude',
- 'include'):
- obj[key] = list(unicode(v) for v in value)
- elif key in ('exponential',
- 'normalizer',
- 'only_absolute',
- 'precision'):
- obj['no_convert'] = True
-
- for flag in (param.flags or []):
- if flag in ('dnsrecord_extra',
- 'dnsrecord_part',
- 'no_option',
- 'suppress_empty'):
- obj[flag] = True
-
- return obj
-
- def _retrieve(self, commandname, name, **kwargs):
- command = self.api.Command[commandname]
-
- if name != 'version':
- try:
- return command.params[name]
- except KeyError:
- try:
- return command.output_params[name]
- except KeyError:
- pass
-
- raise errors.NotFound(
- reason=_("%(pkey)s: %(oname)s not found") % {
- 'pkey': name, 'oname': self.name,
- }
- )
-
- def _search(self, commandname, **kwargs):
- command = self.api.Command[commandname]
-
- result = itertools.chain(
- (p for p in command.params() if p.name != 'version'),
- (p for p in command.output_params()
- if p.name not in command.params))
-
- return result
-
-
-@register()
-class param_show(BaseParamRetrieve):
- __doc__ = _("Display information about a command parameter.")
-
-
-@register()
-class param_find(BaseParamSearch):
- __doc__ = _("Search command parameters.")
-
-
-@register()
-class output(BaseParam):
- takes_params = BaseParam.takes_params + (
- Bool(
- 'no_display?',
- label=_("Do not display"),
- flags={'no_search'},
- ),
- )
-
- def _get_obj(self, command_output, **kwargs):
- command, output = command_output
- required = True
- multivalue = False
-
- if isinstance(output, (Entry, ListOfEntries)):
- type_type = dict
- multivalue = isinstance(output, ListOfEntries)
- elif isinstance(output, (PrimaryKey, ListOfPrimaryKeys)):
- if getattr(command, 'obj', None) and command.obj.primary_key:
- type_type = command.obj.primary_key.type
- else:
- type_type = type(None)
- multivalue = isinstance(output, ListOfPrimaryKeys)
- elif isinstance(output.type, tuple):
- if tuple in output.type or list in output.type:
- type_type = None
- multivalue = True
- else:
- type_type = output.type[0]
- required = type(None) not in output.type
- else:
- type_type = output.type
-
- obj = dict()
- obj['name'] = unicode(output.name)
-
- if type_type is unicode:
- obj['type'] = u'str'
- elif type_type is bytes:
- obj['type'] = u'bytes'
- elif type_type is not None:
- obj['type'] = unicode(type_type.__name__)
-
- if not required:
- obj['required'] = False
-
- if multivalue:
- obj['multivalue'] = True
-
- if 'doc' in output.__dict__:
- obj['doc'] = unicode(output.doc)
-
- if 'flags' in output.__dict__:
- if 'no_display' in output.flags:
- obj['no_display'] = True
-
- return obj
-
- def _retrieve(self, commandname, name, **kwargs):
- command = self.api.Command[commandname]
- try:
- return (command, command.output[name])
- except KeyError:
- raise errors.NotFound(
- reason=_("%(pkey)s: %(oname)s not found") % {
- 'pkey': name, 'oname': self.name,
- }
- )
-
- def _search(self, commandname, **kwargs):
- command = self.api.Command[commandname]
- return ((command, output) for output in command.output())
-
-
-@register()
-class output_show(BaseParamRetrieve):
- __doc__ = _("Display information about a command output.")
-
-
-@register()
-class output_find(BaseParamSearch):
- __doc__ = _("Search for command outputs.")
-
-
-@register()
-class schema(Command):
- NO_CLI = True
-
- def execute(self, *args, **kwargs):
- commands = list(self.api.Object.command.search(**kwargs))
- for command in commands:
- name = command['name']
- command['params'] = list(
- self.api.Object.param.search(name, **kwargs))
- command['output'] = list(
- self.api.Object.output.search(name, **kwargs))
-
- topics = list(self.api.Object.topic.search(**kwargs))
-
- schema = dict()
- schema['version'] = API_VERSION
- schema['commands'] = commands
- schema['topics'] = topics
-
- return dict(result=schema)
diff --git a/ipalib/plugins/selfservice.py b/ipalib/plugins/selfservice.py
deleted file mode 100644
index 4ff6ac744..000000000
--- a/ipalib/plugins/selfservice.py
+++ /dev/null
@@ -1,224 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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 ipalib import _, ngettext
-from ipalib import Str
-from ipalib import api, crud, errors
-from ipalib import output
-from ipalib import Object
-from ipalib.plugable import Registry
-from .baseldap import gen_pkey_only_option, pkey_to_value
-
-__doc__ = _("""
-Self-service Permissions
-
-A permission enables fine-grained delegation of permissions. Access Control
-Rules, or instructions (ACIs), grant permission to permissions to perform
-given tasks such as adding a user, modifying a group, etc.
-
-A Self-service permission defines what an object can change in its own entry.
-
-
-EXAMPLES:
-
- Add a self-service rule to allow users to manage their address (using Bash
- brace expansion):
- ipa selfservice-add --permissions=write --attrs={street,postalCode,l,c,st} "Users manage their own address"
-
- When managing the list of attributes you need to include all attributes
- in the list, including existing ones.
- Add telephoneNumber to the list (using Bash brace expansion):
- ipa selfservice-mod --attrs={street,postalCode,l,c,st,telephoneNumber} "Users manage their own address"
-
- Display our updated rule:
- ipa selfservice-show "Users manage their own address"
-
- Delete a rule:
- ipa selfservice-del "Users manage their own address"
-""")
-
-register = Registry()
-
-ACI_PREFIX=u"selfservice"
-
-output_params = (
- Str('aci',
- label=_('ACI'),
- ),
-)
-
-
-@register()
-class selfservice(Object):
- """
- Selfservice object.
- """
-
- bindable = False
- object_name = _('self service permission')
- object_name_plural = _('self service permissions')
- label = _('Self Service Permissions')
- label_singular = _('Self Service Permission')
-
- takes_params = (
- Str('aciname',
- cli_name='name',
- label=_('Self-service name'),
- doc=_('Self-service name'),
- primary_key=True,
- pattern='^[-_ a-zA-Z0-9]+$',
- pattern_errmsg="May only contain letters, numbers, -, _, and space",
- ),
- Str('permissions*',
- cli_name='permissions',
- label=_('Permissions'),
- doc=_('Permissions to grant (read, write). Default is write.'),
- ),
- Str('attrs+',
- cli_name='attrs',
- label=_('Attributes'),
- doc=_('Attributes to which the permission applies.'),
- normalizer=lambda value: value.lower(),
- ),
- )
-
- def __json__(self):
- json_friendly_attributes = (
- 'label', 'label_singular', 'takes_params', 'bindable', 'name',
- 'object_name', 'object_name_plural',
- )
- json_dict = dict(
- (a, getattr(self, a)) for a in json_friendly_attributes
- )
- json_dict['primary_key'] = self.primary_key.name
- json_dict['methods'] = [m for m in self.methods]
- return json_dict
-
- def postprocess_result(self, result):
- try:
- # do not include prefix in result
- del result['aciprefix']
- except KeyError:
- pass
-
-
-
-@register()
-class selfservice_add(crud.Create):
- __doc__ = _('Add a new self-service permission.')
-
- msg_summary = _('Added selfservice "%(value)s"')
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- if not 'permissions' in kw:
- kw['permissions'] = (u'write',)
- kw['selfaci'] = True
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_add'](aciname, **kw)['result']
- self.obj.postprocess_result(result)
-
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class selfservice_del(crud.Delete):
- __doc__ = _('Delete a self-service permission.')
-
- has_output = output.standard_boolean
- msg_summary = _('Deleted selfservice "%(value)s"')
-
- def execute(self, aciname, **kw):
- result = api.Command['aci_del'](aciname, aciprefix=ACI_PREFIX)
- self.obj.postprocess_result(result)
-
- return dict(
- result=True,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class selfservice_mod(crud.Update):
- __doc__ = _('Modify a self-service permission.')
-
- msg_summary = _('Modified selfservice "%(value)s"')
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- if 'attrs' in kw and kw['attrs'] is None:
- raise errors.RequirementError(name='attrs')
-
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_mod'](aciname, **kw)['result']
- self.obj.postprocess_result(result)
-
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
-
-
-@register()
-class selfservice_find(crud.Search):
- __doc__ = _('Search for a self-service permission.')
-
- msg_summary = ngettext(
- '%(count)d selfservice matched', '%(count)d selfservices matched', 0
- )
-
- takes_options = (gen_pkey_only_option("name"),)
- has_output_params = output_params
-
- def execute(self, term=None, **kw):
- kw['selfaci'] = True
- kw['aciprefix'] = ACI_PREFIX
- result = api.Command['aci_find'](term, **kw)['result']
-
- for aci in result:
- self.obj.postprocess_result(aci)
-
- return dict(
- result=result,
- count=len(result),
- truncated=False,
- )
-
-
-
-@register()
-class selfservice_show(crud.Retrieve):
- __doc__ = _('Display information about a self-service permission.')
-
- has_output_params = output_params
-
- def execute(self, aciname, **kw):
- result = api.Command['aci_show'](aciname, aciprefix=ACI_PREFIX, **kw)['result']
- self.obj.postprocess_result(result)
- return dict(
- result=result,
- value=pkey_to_value(aciname, kw),
- )
-
diff --git a/ipalib/plugins/selinuxusermap.py b/ipalib/plugins/selinuxusermap.py
deleted file mode 100644
index 8f660d089..000000000
--- a/ipalib/plugins/selinuxusermap.py
+++ /dev/null
@@ -1,569 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@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/>.
-
-import re
-
-from ipalib import api, errors
-from ipalib import Str, StrEnum, Bool
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPQuery,
- LDAPAddMember,
- LDAPRemoveMember)
-from ipalib import _, ngettext
-from ipalib import output
-from .hbacrule import is_all
-from ipapython.dn import DN
-
-__doc__ = _("""
-SELinux User Mapping
-
-Map IPA users to SELinux users by host.
-
-Hosts, hostgroups, users and groups can be either defined within
-the rule or it may point to an existing HBAC rule. When using
---hbacrule option to selinuxusermap-find an exact match is made on the
-HBAC rule name, so only one or zero entries will be returned.
-
-EXAMPLES:
-
- Create a rule, "test1", that sets all users to xguest_u:s0 on the host "server":
- ipa selinuxusermap-add --usercat=all --selinuxuser=xguest_u:s0 test1
- ipa selinuxusermap-add-host --hosts=server.example.com test1
-
- Create a rule, "test2", that sets all users to guest_u:s0 and uses an existing HBAC rule for users and hosts:
- ipa selinuxusermap-add --usercat=all --hbacrule=webserver --selinuxuser=guest_u:s0 test2
-
- Display the properties of a rule:
- ipa selinuxusermap-show test2
-
- Create a rule for a specific user. This sets the SELinux context for
- user john to unconfined_u:s0-s0:c0.c1023 on any machine:
- ipa selinuxusermap-add --hostcat=all --selinuxuser=unconfined_u:s0-s0:c0.c1023 john_unconfined
- ipa selinuxusermap-add-user --users=john john_unconfined
-
- Disable a rule:
- ipa selinuxusermap-disable test1
-
- Enable a rule:
- ipa selinuxusermap-enable test1
-
- Find a rule referencing a specific HBAC rule:
- ipa selinuxusermap-find --hbacrule=allow_some
-
- Remove a rule:
- ipa selinuxusermap-del john_unconfined
-
-SEEALSO:
-
- The list controlling the order in which the SELinux user map is applied
- and the default SELinux user are available in the config-show command.
-""")
-
-register = Registry()
-
-notboth_err = _('HBAC rule and local members cannot both be set')
-
-
-def validate_selinuxuser(ugettext, user):
- """
- An SELinux user has 3 components: user:MLS:MCS. user and MLS are required.
- user traditionally ends with _u but this is not mandatory.
- The regex is ^[a-zA-Z][a-zA-Z_]*
-
- The MLS part can only be:
- Level: s[0-15](-s[0-15])
-
- Then MCS could be c[0-1023].c[0-1023] and/or c[0-1023]-c[0-c0123]
- Meaning
- s0 s0-s1 s0-s15:c0.c1023 s0-s1:c0,c2,c15.c26 s0-s0:c0.c1023
-
- Returns a message on invalid, returns nothing on valid.
- """
- regex_name = re.compile(r'^[a-zA-Z][a-zA-Z_]*$')
- regex_mls = re.compile(r'^s[0-9][1-5]{0,1}(-s[0-9][1-5]{0,1}){0,1}$')
- regex_mcs = re.compile(r'^c(\d+)([.,-]c(\d+))*?$')
-
- # If we add in ::: we don't have to check to see if some values are
- # empty
- (name, mls, mcs, ignore) = (user + ':::').split(':', 3)
-
- if not regex_name.match(name):
- return _('Invalid SELinux user name, only a-Z and _ are allowed')
- if not mls or not regex_mls.match(mls):
- return _('Invalid MLS value, must match s[0-15](-s[0-15])')
- m = regex_mcs.match(mcs)
- if mcs and (not m or (m.group(3) and (int(m.group(3)) > 1023))):
- return _('Invalid MCS value, must match c[0-1023].c[0-1023] '
- 'and/or c[0-1023]-c[0-c0123]')
-
- return None
-
-
-def validate_selinuxuser_inlist(ldap, user):
- """
- Ensure the user is in the list of allowed SELinux users.
-
- Returns nothing if the user is found, raises an exception otherwise.
- """
- config = ldap.get_ipa_config()
- item = config.get('ipaselinuxusermaporder', [])
- if len(item) != 1:
- raise errors.NotFound(reason=_('SELinux user map list not '
- 'found in configuration'))
- userlist = item[0].split('$')
- if user not in userlist:
- raise errors.NotFound(
- reason=_('SELinux user %(user)s not found in '
- 'ordering list (in config)') % dict(user=user))
-
- return
-
-
-@register()
-class selinuxusermap(LDAPObject):
- """
- SELinux User Map object.
- """
- container_dn = api.env.container_selinux
- object_name = _('SELinux User Map rule')
- object_name_plural = _('SELinux User Map rules')
- object_class = ['ipaassociation', 'ipaselinuxusermap']
- permission_filter_objectclasses = ['ipaselinuxusermap']
- default_attributes = [
- 'cn', 'ipaenabledflag',
- 'description', 'usercategory', 'hostcategory',
- 'ipaenabledflag', 'memberuser', 'memberhost',
- 'seealso', 'ipaselinuxuser',
- ]
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- attribute_members = {
- 'memberuser': ['user', 'group'],
- 'memberhost': ['host', 'hostgroup'],
- }
- managed_permissions = {
- 'System: Read SELinux User Maps': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'accesstime', 'cn', 'description', 'hostcategory',
- 'ipaenabledflag', 'ipaselinuxuser', 'ipauniqueid',
- 'memberhost', 'memberuser', 'seealso', 'usercategory',
- 'objectclass', 'member',
- },
- },
- 'System: Add SELinux User Maps': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Add SELinux User Maps";allow (add) groupdn = "ldap:///cn=Add SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'SELinux User Map Administrators'},
- },
- 'System: Modify SELinux User Maps': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'cn', 'ipaenabledflag', 'ipaselinuxuser', 'memberhost',
- 'memberuser', 'seealso'
- },
- 'replaces': [
- '(targetattr = "cn || memberuser || memberhost || seealso || ipaselinuxuser || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Modify SELinux User Maps";allow (write) groupdn = "ldap:///cn=Modify SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'SELinux User Map Administrators'},
- },
- 'System: Remove SELinux User Maps': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=usermap,cn=selinux,$SUFFIX")(version 3.0;acl "permission:Remove SELinux User Maps";allow (delete) groupdn = "ldap:///cn=Remove SELinux User Maps,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'SELinux User Map Administrators'},
- },
- }
-
- # These maps will not show as members of other entries
-
- label = _('SELinux User Maps')
- label_singular = _('SELinux User Map')
-
- takes_params = (
- Str('cn',
- cli_name='name',
- label=_('Rule name'),
- primary_key=True,
- ),
- Str('ipaselinuxuser', validate_selinuxuser,
- cli_name='selinuxuser',
- label=_('SELinux User'),
- ),
- Str('seealso?',
- cli_name='hbacrule',
- label=_('HBAC Rule'),
- doc=_('HBAC Rule that defines the users, groups and hostgroups'),
- ),
- StrEnum('usercategory?',
- cli_name='usercat',
- label=_('User category'),
- doc=_('User category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('hostcategory?',
- cli_name='hostcat',
- label=_('Host category'),
- doc=_('Host category the rule applies to'),
- values=(u'all', ),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- Bool('ipaenabledflag?',
- label=_('Enabled'),
- flags=['no_option'],
- ),
- Str('memberuser_user?',
- label=_('Users'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberuser_group?',
- label=_('User Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_host?',
- label=_('Hosts'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_hostgroup?',
- label=_('Host Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- )
-
- def _normalize_seealso(self, seealso):
- """
- Given a HBAC rule name verify its existence and return the dn.
- """
- if not seealso:
- return None
-
- try:
- dn = DN(seealso)
- return str(dn)
- except ValueError:
- try:
- entry_attrs = self.backend.find_entry_by_attr(
- self.api.Object['hbacrule'].primary_key.name,
- seealso,
- self.api.Object['hbacrule'].object_class,
- [''],
- DN(self.api.Object['hbacrule'].container_dn, api.env.basedn))
- seealso = entry_attrs.dn
- except errors.NotFound:
- raise errors.NotFound(reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso))
-
- return seealso
-
- def _convert_seealso(self, ldap, entry_attrs, **options):
- """
- Convert an HBAC rule dn into a name
- """
- if options.get('raw', False):
- return
-
- if 'seealso' in entry_attrs:
- hbac_attrs = ldap.get_entry(entry_attrs['seealso'][0], ['cn'])
- entry_attrs['seealso'] = hbac_attrs['cn'][0]
-
-
-
-@register()
-class selinuxusermap_add(LDAPCreate):
- __doc__ = _('Create a new SELinux User Map.')
-
- msg_summary = _('Added SELinux User Map "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- # rules are enabled by default
- entry_attrs['ipaenabledflag'] = 'TRUE'
- validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser'])
-
- # hbacrule is not allowed when usercat or hostcat is set
- is_to_be_set = lambda x: x in entry_attrs and entry_attrs[x] != None
-
- are_local_members_to_be_set = any(is_to_be_set(attr)
- for attr in ('usercategory',
- 'hostcategory'))
-
- is_hbacrule_to_be_set = is_to_be_set('seealso')
-
- if is_hbacrule_to_be_set and are_local_members_to_be_set:
- raise errors.MutuallyExclusiveError(reason=notboth_err)
-
- if is_hbacrule_to_be_set:
- entry_attrs['seealso'] = self.obj._normalize_seealso(entry_attrs['seealso'])
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj._convert_seealso(ldap, entry_attrs, **options)
-
- return dn
-
-
-
-@register()
-class selinuxusermap_del(LDAPDelete):
- __doc__ = _('Delete a SELinux User Map.')
-
- msg_summary = _('Deleted SELinux User Map "%(value)s"')
-
-
-
-@register()
-class selinuxusermap_mod(LDAPUpdate):
- __doc__ = _('Modify a SELinux User Map.')
-
- msg_summary = _('Modified SELinux User Map "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- try:
- _entry_attrs = ldap.get_entry(dn, attrs_list)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- is_to_be_deleted = lambda x: (x in _entry_attrs and x in entry_attrs) and \
- entry_attrs[x] == None
-
- # makes sure the local members and hbacrule is not set at the same time
- # memberuser or memberhost could have been set using --setattr
- is_to_be_set = lambda x: ((x in _entry_attrs and _entry_attrs[x] != None) or \
- (x in entry_attrs and entry_attrs[x] != None)) and \
- not is_to_be_deleted(x)
-
- are_local_members_to_be_set = any(is_to_be_set(attr)
- for attr in ('usercategory',
- 'hostcategory',
- 'memberuser',
- 'memberhost'))
-
- is_hbacrule_to_be_set = is_to_be_set('seealso')
-
- # this can disable all modifications if hbacrule and local members were
- # set at the same time bypassing this commad, e.g. using ldapmodify
- if are_local_members_to_be_set and is_hbacrule_to_be_set:
- raise errors.MutuallyExclusiveError(reason=notboth_err)
-
- if is_all(entry_attrs, 'usercategory') and 'memberuser' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason="user category "
- "cannot be set to 'all' while there are allowed users")
- if is_all(entry_attrs, 'hostcategory') and 'memberhost' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason="host category "
- "cannot be set to 'all' while there are allowed hosts")
-
- if 'ipaselinuxuser' in entry_attrs:
- validate_selinuxuser_inlist(ldap, entry_attrs['ipaselinuxuser'])
-
- if 'seealso' in entry_attrs:
- entry_attrs['seealso'] = self.obj._normalize_seealso(entry_attrs['seealso'])
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj._convert_seealso(ldap, entry_attrs, **options)
- return dn
-
-
-
-@register()
-class selinuxusermap_find(LDAPSearch):
- __doc__ = _('Search for SELinux User Maps.')
-
- msg_summary = ngettext(
- '%(count)d SELinux User Map matched', '%(count)d SELinux User Maps matched', 0
- )
-
- def execute(self, *args, **options):
- # If searching on hbacrule we need to find the uuid to search on
- if options.get('seealso'):
- hbacrule = options['seealso']
-
- try:
- hbac = api.Command['hbacrule_show'](hbacrule,
-all=True)['result']
- dn = hbac['dn']
- except errors.NotFound:
- return dict(count=0, result=[], truncated=False)
- options['seealso'] = dn
-
- return super(selinuxusermap_find, self).execute(*args, **options)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- for attrs in entries:
- self.obj._convert_seealso(ldap, attrs, **options)
- return truncated
-
-
-
-@register()
-class selinuxusermap_show(LDAPRetrieve):
- __doc__ = _('Display the properties of a SELinux User Map rule.')
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj._convert_seealso(ldap, entry_attrs, **options)
- return dn
-
-
-
-@register()
-class selinuxusermap_enable(LDAPQuery):
- __doc__ = _('Enable an SELinux User Map rule.')
-
- msg_summary = _('Enabled SELinux User Map "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['TRUE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- raise errors.AlreadyActive()
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-
-@register()
-class selinuxusermap_disable(LDAPQuery):
- __doc__ = _('Disable an SELinux User Map rule.')
-
- msg_summary = _('Disabled SELinux User Map "%(value)s"')
- has_output = output.standard_value
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['FALSE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- raise errors.AlreadyInactive()
-
- return dict(
- result=True,
- value=pkey_to_value(cn, options),
- )
-
-
-
-@register()
-class selinuxusermap_add_user(LDAPAddMember):
- __doc__ = _('Add users and groups to an SELinux User Map rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'usercategory' in entry_attrs and \
- entry_attrs['usercategory'][0].lower() == 'all':
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when user category='all'"))
- if 'seealso' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=notboth_err)
- return dn
-
-
-
-@register()
-class selinuxusermap_remove_user(LDAPRemoveMember):
- __doc__ = _('Remove users and groups from an SELinux User Map rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
-
-
-@register()
-class selinuxusermap_add_host(LDAPAddMember):
- __doc__ = _('Add target hosts and hostgroups to an SELinux User Map rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- dn = entry_attrs.dn
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'hostcategory' in entry_attrs and \
- entry_attrs['hostcategory'][0].lower() == 'all':
- raise errors.MutuallyExclusiveError(
- reason=_("hosts cannot be added when host category='all'"))
- if 'seealso' in entry_attrs:
- raise errors.MutuallyExclusiveError(reason=notboth_err)
- return dn
-
-
-
-@register()
-class selinuxusermap_remove_host(LDAPRemoveMember):
- __doc__ = _('Remove target hosts and hostgroups from an SELinux User Map rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
diff --git a/ipalib/plugins/server.py b/ipalib/plugins/server.py
deleted file mode 100644
index 6faaf8ec5..000000000
--- a/ipalib/plugins/server.py
+++ /dev/null
@@ -1,260 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-import dbus
-import dbus.mainloop.glib
-
-from ipalib import api, crud, errors, messages
-from ipalib import Int, Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPSearch,
- LDAPRetrieve,
- LDAPDelete,
- LDAPObject)
-from ipalib.request import context
-from ipalib import _, ngettext
-from ipalib import output
-
-__doc__ = _("""
-IPA servers
-""") + _("""
-Get information about installed IPA servers.
-""") + _("""
-EXAMPLES:
-""") + _("""
- Find all servers:
- ipa server-find
-""") + _("""
- Show specific server:
- ipa server-show ipa.example.com
-""")
-
-register = Registry()
-
-
-@register()
-class server(LDAPObject):
- """
- IPA server
- """
- container_dn = api.env.container_masters
- object_name = _('server')
- object_name_plural = _('servers')
- object_class = ['top']
- search_attributes = ['cn']
- default_attributes = [
- 'cn', 'iparepltopomanagedsuffix', 'ipamindomainlevel',
- 'ipamaxdomainlevel'
- ]
- label = _('IPA Servers')
- label_singular = _('IPA Server')
- attribute_members = {
- 'iparepltopomanagedsuffix': ['topologysuffix'],
- }
- relationships = {
- 'iparepltopomanagedsuffix': ('Managed', '', 'no_'),
- }
- takes_params = (
- Str(
- 'cn',
- cli_name='name',
- primary_key=True,
- label=_('Server name'),
- doc=_('IPA server hostname'),
- ),
- Str(
- 'iparepltopomanagedsuffix*',
- flags={'no_create', 'no_update', 'no_search'},
- ),
- Str(
- 'iparepltopomanagedsuffix_topologysuffix*',
- label=_('Managed suffixes'),
- flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
- ),
- Int(
- 'ipamindomainlevel',
- cli_name='minlevel',
- label=_('Min domain level'),
- doc=_('Minimum domain level'),
- flags={'no_create', 'no_update'},
- ),
- Int(
- 'ipamaxdomainlevel',
- cli_name='maxlevel',
- label=_('Max domain level'),
- doc=_('Maximum domain level'),
- flags={'no_create', 'no_update'},
- ),
- )
-
- def _get_suffixes(self):
- suffixes = self.api.Command.topologysuffix_find(
- all=True, raw=True,
- )['result']
- suffixes = [(s['iparepltopoconfroot'][0], s['dn']) for s in suffixes]
- return suffixes
-
- def _apply_suffixes(self, entry, suffixes):
- # change suffix DNs to topologysuffix entry DNs
- # this fixes LDAPObject.convert_attribute_members() for suffixes
- suffixes = dict(suffixes)
- if 'iparepltopomanagedsuffix' in entry:
- entry['iparepltopomanagedsuffix'] = [
- suffixes.get(m, m) for m in entry['iparepltopomanagedsuffix']
- ]
-
-
-@register()
-class server_find(LDAPSearch):
- __doc__ = _('Search for IPA servers.')
-
- msg_summary = ngettext(
- '%(count)d IPA server matched',
- '%(count)d IPA servers matched', 0
- )
- member_attributes = ['iparepltopomanagedsuffix']
-
- def get_options(self):
- for option in super(server_find, self).get_options():
- if option.name == 'topologysuffix':
- option = option.clone(cli_name='topologysuffixes')
- elif option.name == 'no_topologysuffix':
- option = option.clone(cli_name='no_topologysuffixes')
- yield option
-
- def get_member_filter(self, ldap, **options):
- options.pop('topologysuffix', None)
- options.pop('no_topologysuffix', None)
-
- return super(server_find, self).get_member_filter(ldap, **options)
-
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope,
- *args, **options):
- included = options.get('topologysuffix')
- excluded = options.get('no_topologysuffix')
-
- if included or excluded:
- topologysuffix = self.api.Object.topologysuffix
- suffixes = self.obj._get_suffixes()
- suffixes = {s[1]: s[0] for s in suffixes}
-
- if included:
- included = [topologysuffix.get_dn(pk) for pk in included]
- try:
- included = [suffixes[dn] for dn in included]
- except KeyError:
- # force empty result
- filter = '(!(objectclass=*))'
- else:
- filter = ldap.make_filter_from_attr(
- 'iparepltopomanagedsuffix', included, ldap.MATCH_ALL
- )
- filters = ldap.combine_filters(
- (filters, filter), ldap.MATCH_ALL
- )
-
- if excluded:
- excluded = [topologysuffix.get_dn(pk) for pk in excluded]
- excluded = [suffixes[dn] for dn in excluded if dn in suffixes]
- filter = ldap.make_filter_from_attr(
- 'iparepltopomanagedsuffix', excluded, ldap.MATCH_NONE
- )
- filters = ldap.combine_filters(
- (filters, filter), ldap.MATCH_ALL
- )
-
- return (filters, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if not options.get('raw', False):
- suffixes = self.obj._get_suffixes()
- for entry in entries:
- self.obj._apply_suffixes(entry, suffixes)
-
- return truncated
-
-
-@register()
-class server_show(LDAPRetrieve):
- __doc__ = _('Show IPA server.')
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- if not options.get('raw', False):
- suffixes = self.obj._get_suffixes()
- self.obj._apply_suffixes(entry, suffixes)
-
- return dn
-
-
-@register()
-class server_del(LDAPDelete):
- __doc__ = _('Delete IPA server.')
- NO_CLI = True
- msg_summary = _('Deleted IPA server "%(value)s"')
-
-
-@register()
-class server_conncheck(crud.PKQuery):
- __doc__ = _("Check connection to remote IPA server.")
-
- NO_CLI = True
-
- takes_args = (
- Str(
- 'remote_cn',
- cli_name='remote_name',
- label=_('Remote server name'),
- doc=_('Remote IPA server hostname'),
- ),
- )
-
- has_output = output.standard_value
-
- def execute(self, *keys, **options):
- # the server must be the local host
- if keys[-2] != api.env.host:
- raise errors.ValidationError(
- name='cn', error=_("must be \"%s\"") % api.env.host)
-
- # the server entry must exist
- try:
- self.obj.get_dn_if_exists(*keys[:-1])
- except errors.NotFound:
- self.obj.handle_not_found(keys[-2])
-
- # the user must have the Replication Administrators privilege
- privilege = u'Replication Administrators'
- privilege_dn = self.api.Object.privilege.get_dn(privilege)
- ldap = self.obj.backend
- filter = ldap.make_filter({
- 'krbprincipalname': context.principal, # pylint: disable=no-member
- 'memberof': privilege_dn},
- rules=ldap.MATCH_ALL)
- try:
- ldap.find_entries(base_dn=self.api.env.basedn, filter=filter)
- except errors.NotFound:
- raise errors.ACIError(
- info=_("not allowed to perform server connection check"))
-
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-
- bus = dbus.SystemBus()
- obj = bus.get_object('org.freeipa.server', '/',
- follow_name_owner_changes=True)
- server = dbus.Interface(obj, 'org.freeipa.server')
-
- ret, stdout, stderr = server.conncheck(keys[-1])
-
- result = dict(
- result=(ret == 0),
- value=keys[-2],
- )
-
- for line in stdout.splitlines():
- messages.add_message(options['version'],
- result,
- messages.ExternalCommandOutput(line=line))
-
- return result
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
deleted file mode 100644
index 7e3735583..000000000
--- a/ipalib/plugins/service.py
+++ /dev/null
@@ -1,889 +0,0 @@
-# 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/>.
-
-import six
-
-from ipalib import api, errors
-from ipalib import Bytes, StrEnum, Bool, Str, Flag
-from ipalib.plugable import Registry
-from .baseldap import (
- host_is_master,
- add_missing_object_class,
- pkey_to_value,
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember,
- LDAPQuery,
- LDAPAddAttribute,
- LDAPRemoveAttribute)
-from ipalib import x509
-from ipalib import _, ngettext
-from ipalib import util
-from ipalib import output
-from ipapython.dn import DN
-
-import nss.nss as nss
-
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Services
-
-A IPA service represents a service that runs on a host. The IPA service
-record can store a Kerberos principal, an SSL certificate, or both.
-
-An IPA service can be managed directly from a machine, provided that
-machine has been given the correct permission. This is true even for
-machines other than the one the service is associated with. For example,
-requesting an SSL certificate using the host service principal credentials
-of the host. To manage a service using host credentials you need to
-kinit as the host:
-
- # kinit -kt /etc/krb5.keytab host/ipa.example.com@EXAMPLE.COM
-
-Adding an IPA service allows the associated service to request an SSL
-certificate or keytab, but this is performed as a separate step; they
-are not produced as a result of adding the service.
-
-Only the public aspect of a certificate is stored in a service record;
-the private key is not stored.
-
-EXAMPLES:
-
- Add a new IPA service:
- ipa service-add HTTP/web.example.com
-
- Allow a host to manage an IPA service certificate:
- ipa service-add-host --hosts=web.example.com HTTP/web.example.com
- ipa role-add-member --hosts=web.example.com certadmin
-
- Override a default list of supported PAC types for the service:
- ipa service-mod HTTP/web.example.com --pac-type=MS-PAC
-
- A typical use case where overriding the PAC type is needed is NFS.
- Currently the related code in the Linux kernel can only handle Kerberos
- tickets up to a maximal size. Since the PAC data can become quite large it
- is recommended to set --pac-type=NONE for NFS services.
-
- Delete an IPA service:
- ipa service-del HTTP/web.example.com
-
- Find all IPA services associated with a host:
- ipa service-find web.example.com
-
- Find all HTTP services:
- ipa service-find HTTP
-
- Disable the service Kerberos key and SSL certificate:
- ipa service-disable HTTP/web.example.com
-
- Request a certificate for an IPA service:
- ipa cert-request --principal=HTTP/web.example.com example.csr
-""") + _("""
- Allow user to create a keytab:
- ipa service-allow-create-keytab HTTP/web.example.com --users=tuser1
-""") + _("""
- Generate and retrieve a keytab for an IPA service:
- ipa-getkeytab -s ipa.example.com -p HTTP/web.example.com -k /etc/httpd/httpd.keytab
-
-""")
-
-register = Registry()
-
-output_params = (
- Flag('has_keytab',
- label=_('Keytab'),
- ),
- Str('managedby_host',
- label='Managed by',
- ),
- Str('subject',
- label=_('Subject'),
- ),
- Str('serial_number',
- label=_('Serial Number'),
- ),
- Str('serial_number_hex',
- label=_('Serial Number (hex)'),
- ),
- Str('issuer',
- label=_('Issuer'),
- ),
- Str('valid_not_before',
- label=_('Not Before'),
- ),
- Str('valid_not_after',
- label=_('Not After'),
- ),
- Str('md5_fingerprint',
- label=_('Fingerprint (MD5)'),
- ),
- Str('sha1_fingerprint',
- label=_('Fingerprint (SHA1)'),
- ),
- Str('revocation_reason?',
- label=_('Revocation reason'),
- ),
- Str('ipaallowedtoperform_read_keys_user',
- label=_('Users allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_group',
- label=_('Groups allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_host',
- label=_('Hosts allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_read_keys_hostgroup',
- label=_('Host Groups allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_user',
- label=_('Users allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_group',
- label=_('Groups allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_host',
- label=_('Hosts allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_write_keys_hostgroup',
- label=_('Host Groups allowed to create keytab'),
- ),
- Str('ipaallowedtoperform_read_keys',
- label=_('Failed allowed to retrieve keytab'),
- ),
- Str('ipaallowedtoperform_write_keys',
- label=_('Failed allowed to create keytab'),
- ),
-)
-
-ticket_flags_params = (
- Bool('ipakrbrequirespreauth?',
- cli_name='requires_pre_auth',
- label=_('Requires pre-authentication'),
- doc=_('Pre-authentication is required for the service'),
- flags=['virtual_attribute', 'no_search'],
- ),
- Bool('ipakrbokasdelegate?',
- cli_name='ok_as_delegate',
- label=_('Trusted for delegation'),
- doc=_('Client credentials may be delegated to the service'),
- flags=['virtual_attribute', 'no_search'],
- ),
-)
-
-_ticket_flags_map = {
- 'ipakrbrequirespreauth': 0x00000080,
- 'ipakrbokasdelegate': 0x00100000,
-}
-
-_ticket_flags_default = _ticket_flags_map['ipakrbrequirespreauth']
-
-def split_any_principal(principal):
- service = hostname = realm = None
-
- # Break down the principal into its component parts, which may or
- # may not include the realm.
- sp = principal.split('/')
- name_and_realm = None
- if len(sp) > 2:
- raise errors.MalformedServicePrincipal(reason=_('unable to determine service'))
- elif len(sp) == 2:
- service = sp[0]
- if len(service) == 0:
- raise errors.MalformedServicePrincipal(reason=_('blank service'))
- name_and_realm = sp[1]
- else:
- name_and_realm = sp[0]
-
- sr = name_and_realm.split('@')
- if len(sr) > 2:
- raise errors.MalformedServicePrincipal(
- reason=_('unable to determine realm'))
-
- hostname = sr[0].lower()
- if len(sr) == 2:
- realm = sr[1].upper()
- # At some point we'll support multiple realms
- if realm != api.env.realm:
- raise errors.RealmMismatch()
- else:
- realm = api.env.realm
-
- # Note that realm may be None.
- return service, hostname, realm
-
-def split_principal(principal):
- service, name, realm = split_any_principal(principal)
- if service is None:
- raise errors.MalformedServicePrincipal(reason=_('missing service'))
- return service, name, realm
-
-def validate_principal(ugettext, principal):
- (service, hostname, principal) = split_principal(principal)
- return None
-
-def normalize_principal(principal):
- # The principal is already validated when it gets here
- (service, hostname, realm) = split_principal(principal)
- # Put the principal back together again
- principal = '%s/%s@%s' % (service, hostname, realm)
- return unicode(principal)
-
-def validate_certificate(ugettext, cert):
- """
- Check whether the certificate is properly encoded to DER
- """
- if api.env.in_server:
- x509.validate_certificate(cert, datatype=x509.DER)
-
-
-def revoke_certs(certs, logger=None):
- """
- revoke the certificates removed from host/service entry
- """
- for cert in certs:
- try:
- cert = x509.normalize_certificate(cert)
- except errors.CertificateFormatError as e:
- if logger is not None:
- logger.info("Problem decoding certificate: %s" % e)
-
- serial = unicode(x509.get_serial_number(cert, x509.DER))
-
- try:
- result = api.Command['cert_show'](unicode(serial))['result']
- except errors.CertificateOperationError:
- continue
- if 'revocation_reason' in result:
- continue
- if x509.normalize_certificate(result['certificate']) != cert:
- continue
-
- try:
- api.Command['cert_revoke'](unicode(serial),
- revocation_reason=4)
- except errors.NotImplementedError:
- # some CA's might not implement revoke
- pass
-
-
-
-def set_certificate_attrs(entry_attrs):
- """
- Set individual attributes from some values from a certificate.
-
- entry_attrs is a dict of an entry
-
- returns nothing
- """
- if not 'usercertificate' in entry_attrs:
- return
- if type(entry_attrs['usercertificate']) in (list, tuple):
- cert = entry_attrs['usercertificate'][0]
- else:
- cert = entry_attrs['usercertificate']
- cert = x509.normalize_certificate(cert)
- cert = x509.load_certificate(cert, datatype=x509.DER)
- entry_attrs['subject'] = unicode(cert.subject)
- entry_attrs['serial_number'] = unicode(cert.serial_number)
- entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number
- entry_attrs['issuer'] = unicode(cert.issuer)
- entry_attrs['valid_not_before'] = unicode(cert.valid_not_before_str)
- entry_attrs['valid_not_after'] = unicode(cert.valid_not_after_str)
- entry_attrs['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0])
- entry_attrs['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0])
-
-def check_required_principal(ldap, hostname, service):
- """
- Raise an error if the host of this prinicipal is an IPA master and one
- of the principals required for proper execution.
- """
- try:
- host_is_master(ldap, hostname)
- except errors.ValidationError as e:
- service_types = ['HTTP', 'ldap', 'DNS', 'dogtagldap']
- if service in service_types:
- raise errors.ValidationError(name='principal', error=_('This principal is required by the IPA master'))
-
-def update_krbticketflags(ldap, entry_attrs, attrs_list, options, existing):
- add = remove = 0
-
- for (name, value) in _ticket_flags_map.items():
- if name not in options:
- continue
- if options[name]:
- add |= value
- else:
- remove |= value
-
- if not add and not remove:
- return
-
- if 'krbticketflags' not in entry_attrs and existing:
- old_entry_attrs = ldap.get_entry(entry_attrs.dn, ['krbticketflags'])
- else:
- old_entry_attrs = entry_attrs
-
- try:
- ticket_flags = old_entry_attrs.single_value['krbticketflags']
- ticket_flags = int(ticket_flags)
- except (KeyError, ValueError):
- ticket_flags = _ticket_flags_default
-
- ticket_flags |= add
- ticket_flags &= ~remove
-
- entry_attrs['krbticketflags'] = [ticket_flags]
- attrs_list.append('krbticketflags')
-
-def set_kerberos_attrs(entry_attrs, options):
- if options.get('raw', False):
- return
-
- try:
- ticket_flags = entry_attrs.single_value.get('krbticketflags',
- _ticket_flags_default)
- ticket_flags = int(ticket_flags)
- except ValueError:
- return
-
- all_opt = options.get('all', False)
-
- for (name, value) in _ticket_flags_map.items():
- if name in options or all_opt:
- entry_attrs[name] = bool(ticket_flags & value)
-
-def rename_ipaallowedtoperform_from_ldap(entry_attrs, options):
- if options.get('raw', False):
- return
-
- for subtype in ('read_keys', 'write_keys'):
- name = 'ipaallowedtoperform;%s' % subtype
- if name in entry_attrs:
- new_name = 'ipaallowedtoperform_%s' % subtype
- entry_attrs[new_name] = entry_attrs.pop(name)
-
-def rename_ipaallowedtoperform_to_ldap(entry_attrs):
- for subtype in ('read_keys', 'write_keys'):
- name = 'ipaallowedtoperform_%s' % subtype
- if name in entry_attrs:
- new_name = 'ipaallowedtoperform;%s' % subtype
- entry_attrs[new_name] = entry_attrs.pop(name)
-
-@register()
-class service(LDAPObject):
- """
- Service object.
- """
- container_dn = api.env.container_service
- object_name = _('service')
- object_name_plural = _('services')
- object_class = [
- 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject',
- 'ipaservice', 'pkiuser'
- ]
- possible_objectclasses = ['ipakrbprincipal', 'ipaallowedoperations']
- permission_filter_objectclasses = ['ipaservice']
- search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata']
- default_attributes = ['krbprincipalname', 'usercertificate', 'managedby',
- 'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform', 'krbprincipalauthind']
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'managedby': ['host'],
- 'memberof': ['role'],
- 'ipaallowedtoperform_read_keys': ['user', 'group', 'host', 'hostgroup'],
- 'ipaallowedtoperform_write_keys': ['user', 'group', 'host', 'hostgroup'],
- }
- bindable = True
- relationships = {
- 'managedby': ('Managed by', 'man_by_', 'not_man_by_'),
- 'ipaallowedtoperform_read_keys': ('Allow to retrieve keytab by', 'retrieve_keytab_by_', 'not_retrieve_keytab_by_'),
- 'ipaallowedtoperform_write_keys': ('Allow to create keytab by', 'write_keytab_by_', 'not_write_keytab_by'),
- }
- password_attributes = [('krbprincipalkey', 'has_keytab')]
- managed_permissions = {
- 'System: Read Services': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass',
- 'ipauniqueid', 'managedby', 'memberof', 'usercertificate',
- 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases',
- 'krbprincipalexpiration', 'krbpasswordexpiration',
- 'krblastpwdchange', 'ipakrbauthzdata', 'ipakrbprincipalalias',
- 'krbobjectreferences',
- },
- },
- 'System: Add Services': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Services";allow (add) groupdn = "ldap:///cn=Add Services,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Service Administrators'},
- },
- 'System: Manage Service Keytab': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'},
- 'replaces': [
- '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage service keytab";allow (write) groupdn = "ldap:///cn=Manage service keytab,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Service Administrators', 'Host Administrators'},
- },
- 'System: Manage Service Keytab Permissions': {
- 'ipapermright': {'read', 'search', 'compare', 'write'},
- 'ipapermdefaultattr': {
- 'ipaallowedtoperform;write_keys',
- 'ipaallowedtoperform;read_keys', 'objectclass'
- },
- 'default_privileges': {'Service Administrators', 'Host Administrators'},
- },
- 'System: Modify Services': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'usercertificate'},
- 'replaces': [
- '(targetattr = "usercertificate")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Services";allow (write) groupdn = "ldap:///cn=Modify Services,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Service Administrators'},
- },
- 'System: Remove Services': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Services";allow (delete) groupdn = "ldap:///cn=Remove Services,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Service Administrators'},
- },
- }
-
- label = _('Services')
- label_singular = _('Service')
-
- takes_params = (
- Str('krbprincipalname', validate_principal,
- cli_name='principal',
- label=_('Principal'),
- doc=_('Service principal'),
- primary_key=True,
- normalizer=lambda value: normalize_principal(value),
- ),
- Bytes('usercertificate*', validate_certificate,
- cli_name='certificate',
- label=_('Certificate'),
- doc=_('Base-64 encoded service certificate'),
- flags=['no_search',],
- ),
- StrEnum('ipakrbauthzdata*',
- cli_name='pac_type',
- label=_('PAC type'),
- doc=_("Override default list of supported PAC types."
- " Use 'NONE' to disable PAC support for this service,"
- " e.g. this might be necessary for NFS services."),
- values=(u'MS-PAC', u'PAD', u'NONE'),
- ),
- Str('krbprincipalauthind*',
- cli_name='auth_ind',
- label=_('Authentication Indicators'),
- doc=_("Defines a whitelist for Authentication Indicators."
- " Use 'otp' to allow OTP-based 2FA authentications."
- " Use 'radius' to allow RADIUS-based 2FA authentications."
- " Other values may be used for custom configurations."),
- ),
- ) + ticket_flags_params
-
- def validate_ipakrbauthzdata(self, entry):
- new_value = entry.get('ipakrbauthzdata', [])
-
- if not new_value:
- return
-
- if not isinstance(new_value, (list, tuple)):
- new_value = set([new_value])
- else:
- new_value = set(new_value)
-
- if u'NONE' in new_value and len(new_value) > 1:
- raise errors.ValidationError(name='ipakrbauthzdata',
- error=_('NONE value cannot be combined with other PAC types'))
-
- def get_dn(self, *keys, **kwargs):
- keys = (normalize_principal(k) for k in keys)
- return super(service, self).get_dn(*keys, **kwargs)
-
-
-@register()
-class service_add(LDAPCreate):
- __doc__ = _('Add a new IPA service.')
-
- msg_summary = _('Added service "%(value)s"')
- member_attributes = ['managedby']
- has_output_params = LDAPCreate.has_output_params + output_params
- takes_options = LDAPCreate.takes_options + (
- Flag('force',
- label=_('Force'),
- doc=_('force principal name even if not in DNS'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- (service, hostname, realm) = split_principal(keys[-1])
- if service.lower() == 'host' and not options['force']:
- raise errors.HostService()
-
- try:
- hostresult = api.Command['host_show'](hostname)['result']
- except errors.NotFound:
- raise errors.NotFound(
- reason=_("The host '%s' does not exist to add a service to.") %
- hostname)
-
- self.obj.validate_ipakrbauthzdata(entry_attrs)
-
- certs = options.get('usercertificate', [])
- certs_der = [x509.normalize_certificate(c) for c in certs]
- for dercert in certs_der:
- x509.verify_cert_subject(ldap, hostname, dercert)
- entry_attrs['usercertificate'] = certs_der
-
- if not options.get('force', False):
- # We know the host exists if we've gotten this far but we
- # really want to discourage creating services for hosts that
- # don't exist in DNS.
- util.verify_host_resolvable(hostname)
- if not 'managedby' in entry_attrs:
- entry_attrs['managedby'] = hostresult['dn']
-
- # Enforce ipaKrbPrincipalAlias to aid case-insensitive searches
- # as krbPrincipalName/krbCanonicalName are case-sensitive in Kerberos
- # schema
- entry_attrs['ipakrbprincipalalias'] = keys[-1]
-
- # Objectclass ipakrbprincipal providing ipakrbprincipalalias is not in
- # in a list of default objectclasses, add it manually
- entry_attrs['objectclass'].append('ipakrbprincipal')
-
- update_krbticketflags(ldap, entry_attrs, attrs_list, options, False)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- return dn
-
-
-
-@register()
-class service_del(LDAPDelete):
- __doc__ = _('Delete an IPA service.')
-
- msg_summary = _('Deleted service "%(value)s"')
- member_attributes = ['managedby']
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- # In the case of services we don't want IPA master services to be
- # deleted. This is a limited few though. If the user has their own
- # custom services allow them to manage them.
- (service, hostname, realm) = split_principal(keys[-1])
- check_required_principal(ldap, hostname, service)
- if self.api.Command.ca_is_enabled()['result']:
- try:
- entry_attrs = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- revoke_certs(entry_attrs.get('usercertificate', []), self.log)
-
- return dn
-
-
-
-@register()
-class service_mod(LDAPUpdate):
- __doc__ = _('Modify an existing IPA service.')
-
- msg_summary = _('Modified service "%(value)s"')
- takes_options = LDAPUpdate.takes_options
- has_output_params = LDAPUpdate.has_output_params + output_params
-
- member_attributes = ['managedby']
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- self.obj.validate_ipakrbauthzdata(entry_attrs)
-
- (service, hostname, realm) = split_principal(keys[-1])
-
- # verify certificates
- certs = entry_attrs.get('usercertificate') or []
- certs_der = [x509.normalize_certificate(c) for c in certs]
- for dercert in certs_der:
- x509.verify_cert_subject(ldap, hostname, dercert)
- # revoke removed certificates
- if certs and self.api.Command.ca_is_enabled()['result']:
- try:
- entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- old_certs = entry_attrs_old.get('usercertificate', [])
- old_certs_der = [x509.normalize_certificate(c) for c in old_certs]
- removed_certs_der = set(old_certs_der) - set(certs_der)
- revoke_certs(removed_certs_der, self.log)
-
- if certs:
- entry_attrs['usercertificate'] = certs_der
-
- update_krbticketflags(ldap, entry_attrs, attrs_list, options, True)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- return dn
-
-
-
-@register()
-class service_find(LDAPSearch):
- __doc__ = _('Search for IPA services.')
-
- msg_summary = ngettext(
- '%(count)d service matched', '%(count)d services matched', 0
- )
- member_attributes = ['managedby']
- takes_options = LDAPSearch.takes_options
- has_output_params = LDAPSearch.has_output_params + output_params
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
- assert isinstance(base_dn, DN)
- # lisp style!
- custom_filter = '(&(objectclass=ipaService)' \
- '(!(objectClass=posixAccount))' \
- '(!(|(krbprincipalname=kadmin/*)' \
- '(krbprincipalname=K/M@*)' \
- '(krbprincipalname=krbtgt/*))' \
- ')' \
- ')'
- return (
- ldap.combine_filters((custom_filter, filter), rules=ldap.MATCH_ALL),
- base_dn, scope
- )
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- for entry_attrs in entries:
- self.obj.get_password_attributes(ldap, entry_attrs.dn, entry_attrs)
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- return truncated
-
-
-
-@register()
-class service_show(LDAPRetrieve):
- __doc__ = _('Display information about an IPA service.')
-
- member_attributes = ['managedby']
- takes_options = LDAPRetrieve.takes_options + (
- Str('out?',
- doc=_('file to store certificate in'),
- ),
- )
- has_output_params = LDAPRetrieve.has_output_params + output_params
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
-
- set_certificate_attrs(entry_attrs)
- set_kerberos_attrs(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
-
- return dn
-
-
-@register()
-class service_add_host(LDAPAddMember):
- __doc__ = _('Add hosts that can manage this service.')
-
- member_attributes = ['managedby']
- has_output_params = LDAPAddMember.has_output_params + output_params
-
-
-
-@register()
-class service_remove_host(LDAPRemoveMember):
- __doc__ = _('Remove hosts that can manage this service.')
-
- member_attributes = ['managedby']
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
-
-@register()
-class service_allow_retrieve_keytab(LDAPAddMember):
- __doc__ = _('Allow users, groups, hosts or host groups to retrieve a keytab'
- ' of this service.')
- member_attributes = ['ipaallowedtoperform_read_keys']
- has_output_params = LDAPAddMember.has_output_params + output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- add_missing_object_class(ldap, u'ipaallowedoperations', dn)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class service_disallow_retrieve_keytab(LDAPRemoveMember):
- __doc__ = _('Disallow users, groups, hosts or host groups to retrieve a '
- 'keytab of this service.')
- member_attributes = ['ipaallowedtoperform_read_keys']
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class service_allow_create_keytab(LDAPAddMember):
- __doc__ = _('Allow users, groups, hosts or host groups to create a keytab '
- 'of this service.')
- member_attributes = ['ipaallowedtoperform_write_keys']
- has_output_params = LDAPAddMember.has_output_params + output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- add_missing_object_class(ldap, u'ipaallowedoperations', dn)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class service_disallow_create_keytab(LDAPRemoveMember):
- __doc__ = _('Disallow users, groups, hosts or host groups to create a '
- 'keytab of this service.')
- member_attributes = ['ipaallowedtoperform_write_keys']
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- rename_ipaallowedtoperform_to_ldap(found)
- rename_ipaallowedtoperform_to_ldap(not_found)
- return dn
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- rename_ipaallowedtoperform_from_ldap(entry_attrs, options)
- rename_ipaallowedtoperform_from_ldap(failed, options)
- return (completed, dn)
-
-
-@register()
-class service_disable(LDAPQuery):
- __doc__ = _('Disable the Kerberos key and SSL certificate of a service.')
-
- has_output = output.standard_value
- msg_summary = _('Disabled service "%(value)s"')
- has_output_params = LDAPQuery.has_output_params + output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(*keys, **options)
- entry_attrs = ldap.get_entry(dn, ['usercertificate'])
-
- (service, hostname, realm) = split_principal(keys[-1])
- check_required_principal(ldap, hostname, service)
-
- # See if we do any work at all here and if not raise an exception
- done_work = False
-
- if self.api.Command.ca_is_enabled()['result']:
- certs = entry_attrs.get('usercertificate', [])
-
- if len(certs) > 0:
- revoke_certs(certs, self.log)
- # Remove the usercertificate altogether
- entry_attrs['usercertificate'] = None
- ldap.update_entry(entry_attrs)
- done_work = True
-
- self.obj.get_password_attributes(ldap, dn, entry_attrs)
- if entry_attrs['has_keytab']:
- ldap.remove_principal_key(dn)
- done_work = True
-
- if not done_work:
- raise errors.AlreadyInactive()
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
-
-@register()
-class service_add_cert(LDAPAddAttribute):
- __doc__ = _('Add new certificates to a service')
- msg_summary = _('Added certificates to service principal "%(value)s"')
- attribute = 'usercertificate'
-
-
-@register()
-class service_remove_cert(LDAPRemoveAttribute):
- __doc__ = _('Remove certificates from a service')
- msg_summary = _('Removed certificates from service principal "%(value)s"')
- attribute = 'usercertificate'
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- if 'usercertificate' in options:
- revoke_certs(options['usercertificate'], self.log)
-
- return dn
diff --git a/ipalib/plugins/servicedelegation.py b/ipalib/plugins/servicedelegation.py
deleted file mode 100644
index 958c3b739..000000000
--- a/ipalib/plugins/servicedelegation.py
+++ /dev/null
@@ -1,550 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-import six
-
-from ipalib import api
-from ipalib import Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPAddMember,
- LDAPRemoveMember,
- LDAPCreate,
- LDAPDelete,
- LDAPSearch,
- LDAPRetrieve)
-from .service import normalize_principal
-from ipalib import _, ngettext
-from ipalib import errors
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Service Constrained Delegation
-
-Manage rules to allow constrained delegation of credentials so
-that a service can impersonate a user when communicating with another
-service without requiring the user to actually forward their TGT.
-This makes for a much better method of delegating credentials as it
-prevents exposure of the short term secret of the user.
-
-The naming convention is to append the word "target" or "targets" to
-a matching rule name. This is not mandatory but helps conceptually
-to associate rules and targets.
-
-A rule consists of two things:
- - A list of targets the rule applies to
- - A list of memberPrincipals that are allowed to delegate for
- those targets
-
-A target consists of a list of principals that can be delegated.
-
-In English, a rule says that this principal can delegate as this
-list of principals, as defined by these targets.
-
-EXAMPLES:
-
- Add a new constrained delegation rule:
- ipa servicedelegationrule-add ftp-delegation
-
- Add a new constrained delegation target:
- ipa servicedelegationtarget-add ftp-delegation-target
-
- Add a principal to the rule:
- ipa servicedelegationrule-add-member --principals=ftp/ipa.example.com \
- ftp-delegation
-
- Add our target to the rule:
- ipa servicedelegationrule-add-target \
- --servicedelegationtargets=ftp-delegation-target ftp-delegation
-
- Add a principal to the target:
- ipa servicedelegationtarget-add-member --principals=ldap/ipa.example.com \
- ftp-delegation-target
-
- Display information about a named delegation rule and target:
- ipa servicedelegationrule_show ftp-delegation
- ipa servicedelegationtarget_show ftp-delegation-target
-
- Remove a constrained delegation:
- ipa servicedelegationrule-del ftp-delegation-target
- ipa servicedelegationtarget-del ftp-delegation
-
-In this example the ftp service can get a TGT for the ldap service on
-the bound user's behalf.
-
-It is strongly discouraged to modify the delegations that ship with
-IPA, ipa-http-delegation and its targets ipa-cifs-delegation-targets and
-ipa-ldap-delegation-targets. Incorrect changes can remove the ability
-to delegate, causing the framework to stop functioning.
-""")
-
-register = Registry()
-
-PROTECTED_CONSTRAINT_RULES = (
- u'ipa-http-delegation',
-)
-
-PROTECTED_CONSTRAINT_TARGETS = (
- u'ipa-cifs-delegation-targets',
- u'ipa-ldap-delegation-targets',
-
-)
-
-
-output_params = (
- Str(
- 'ipaallowedtarget_servicedelegationtarget',
- label=_('Allowed Target'),
- ),
- Str(
- 'ipaallowedtoimpersonate',
- label=_('Allowed to Impersonate'),
- ),
- Str(
- 'memberprincipal',
- label=_('Member principals'),
- ),
- Str(
- 'failed_memberprincipal',
- label=_('Failed members'),
- ),
- Str(
- 'ipaallowedtarget',
- label=_('Failed targets'),
- ),
-)
-
-
-class servicedelegation(LDAPObject):
- """
- Service Constrained Delegation base object.
-
- This jams a couple of concepts into a single plugin because the
- data is all stored in one place. There is a "rule" which has the
- objectclass ipakrb5delegationacl. This is the entry that controls
- the delegation. Other entries that lack this objectclass are
- targets and define what services can be impersonated.
- """
- container_dn = api.env.container_s4u2proxy
- object_class = ['groupofprincipals', 'top']
-
- managed_permissions = {
- 'System: Read Service Delegations': {
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermtargetfilter': {'(objectclass=groupofprincipals)'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass', 'memberprincipal',
- 'ipaallowedtarget',
- },
- 'default_privileges': {'Service Administrators'},
- },
- 'System: Add Service Delegations': {
- 'ipapermright': {'add'},
- 'ipapermtargetfilter': {'(objectclass=groupofprincipals)'},
- 'default_privileges': {'Service Administrators'},
- },
- 'System: Remove Service Delegations': {
- 'ipapermright': {'delete'},
- 'ipapermtargetfilter': {'(objectclass=groupofprincipals)'},
- 'default_privileges': {'Service Administrators'},
- },
- 'System: Modify Service Delegation Membership': {
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': {'(objectclass=groupofprincipals)'},
- 'ipapermdefaultattr': {'memberprincipal', 'ipaallowedtarget'},
- 'default_privileges': {'Service Administrators'},
- },
- }
-
- rdn_is_primary_key = True
-
- takes_params = (
- Str(
- 'cn',
- pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_ .-]{0,253}[a-zA-Z0-9_.-]?$',
- pattern_errmsg='may only include letters, numbers, _, -, ., '
- 'and a space inside',
- maxlength=255,
- cli_name='delegation_name',
- label=_('Delegation name'),
- primary_key=True,
- ),
- )
-
-
-class servicedelegation_add_member(LDAPAddMember):
- __doc__ = _('Add target to a named service delegation.')
- member_attrs = ['memberprincipal']
- member_attributes = []
- member_names = {}
- principal_attr = 'memberprincipal'
- principal_failedattr = 'failed_memberprincipal'
-
- has_output_params = LDAPAddMember.has_output_params + output_params
-
- def get_options(self):
- for option in super(servicedelegation_add_member, self).get_options():
- yield option
- for attr in self.member_attrs:
- name = self.member_names[attr]
- doc = self.member_param_doc % name
- yield Str('%s*' % name, cli_name='%ss' % name, doc=doc,
- label=_('member %s') % name, alwaysask=True)
-
- def get_member_dns(self, **options):
- """
- There are no member_dns to return. memberPrincipal needs
- special handling since it is just a principal, not a
- full dn.
- """
- return dict(), dict()
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- """
- Add memberPrincipal values. This is done afterward because it isn't
- a DN and the LDAPAddMember method explicitly only handles DNs.
-
- A separate fake attribute name is used for failed members. This is
- a reverse of the way this is typically handled in the *Member
- routines, where a successful addition will be represented as
- member/memberof_<attribute>. In this case, because memberPrincipal
- isn't a DN, I'm doing the reverse, and creating a fake failed
- attribute instead.
- """
- ldap = self.obj.backend
- members = []
- failed[self.principal_failedattr] = {}
- failed[self.principal_failedattr][self.principal_attr] = []
- names = options.get(self.member_names[self.principal_attr], [])
- ldap_obj = self.api.Object['service']
- if names:
- for name in names:
- if not name:
- continue
- name = normalize_principal(name)
- obj_dn = ldap_obj.get_dn(name)
- try:
- ldap.get_entry(obj_dn, ['krbprincipalname'])
- except errors.NotFound as e:
- failed[self.principal_failedattr][
- self.principal_attr].append((name, unicode(e)))
- continue
- try:
- if name not in entry_attrs.get(self.principal_attr, []):
- members.append(name)
- else:
- raise errors.AlreadyGroupMember()
- except errors.PublicError as e:
- failed[self.principal_failedattr][
- self.principal_attr].append((name, unicode(e)))
- else:
- completed += 1
-
- if members:
- value = entry_attrs.setdefault(self.principal_attr, [])
- value.extend(members)
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return completed, dn
-
-
-class servicedelegation_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove member from a named service delegation.')
-
- member_attrs = ['memberprincipal']
- member_attributes = []
- member_names = {}
- principal_attr = 'memberprincipal'
- principal_failedattr = 'failed_memberprincipal'
-
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
- def get_options(self):
- for option in super(
- servicedelegation_remove_member, self).get_options():
- yield option
- for attr in self.member_attrs:
- name = self.member_names[attr]
- doc = self.member_param_doc % name
- yield Str('%s*' % name, cli_name='%ss' % name, doc=doc,
- label=_('member %s') % name, alwaysask=True)
-
- def get_member_dns(self, **options):
- """
- Need to ignore memberPrincipal for now and handle the difference
- in objectclass between a rule and a target.
- """
- dns = {}
- failed = {}
- for attr in self.member_attrs:
- dns[attr] = {}
- if attr.lower() == 'memberprincipal':
- # This will be handled later. memberprincipal isn't a
- # DN so will blow up in assertions in baseldap.
- continue
- failed[attr] = {}
- for ldap_obj_name in self.obj.attribute_members[attr]:
- dns[attr][ldap_obj_name] = []
- failed[attr][ldap_obj_name] = []
- names = options.get(self.member_names[attr], [])
- if not names:
- continue
- for name in names:
- if not name:
- continue
- ldap_obj = self.api.Object[ldap_obj_name]
- try:
- dns[attr][ldap_obj_name].append(ldap_obj.get_dn(name))
- except errors.PublicError as e:
- failed[attr][ldap_obj_name].append((name, unicode(e)))
- return dns, failed
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- """
- Remove memberPrincipal values. This is done afterward because it
- isn't a DN and the LDAPAddMember method explicitly only handles DNs.
-
- See servicedelegation_add_member() for an explanation of what
- failedattr is.
- """
- ldap = self.obj.backend
- failed[self.principal_failedattr] = {}
- failed[self.principal_failedattr][self.principal_attr] = []
- names = options.get(self.member_names[self.principal_attr], [])
- if names:
- for name in names:
- if not name:
- continue
- name = normalize_principal(name)
- try:
- if name in entry_attrs.get(self.principal_attr, []):
- entry_attrs[self.principal_attr].remove(name)
- else:
- raise errors.NotGroupMember()
- except errors.PublicError as e:
- failed[self.principal_failedattr][
- self.principal_attr].append((name, unicode(e)))
- else:
- completed += 1
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return completed, dn
-
-
-@register()
-class servicedelegationrule(servicedelegation):
- """
- A service delegation rule. This is the ACL that controls
- what can be delegated to whom.
- """
- object_name = _('service delegation rule')
- object_name_plural = _('service delegation rules')
- object_class = ['ipakrb5delegationacl', 'groupofprincipals', 'top']
- default_attributes = [
- 'cn', 'memberprincipal', 'ipaallowedtarget',
- 'ipaallowedtoimpersonate',
- ]
- attribute_members = {
- # memberprincipal is not listed because it isn't a DN
- 'ipaallowedtarget': ['servicedelegationtarget'],
- }
-
- label = _('Service delegation rules')
- label_singular = _('Service delegation rule')
-
-
-@register()
-class servicedelegationrule_add(LDAPCreate):
- __doc__ = _('Create a new service delegation rule.')
-
- msg_summary = _('Added service delegation rule "%(value)s"')
-
-
-@register()
-class servicedelegationrule_del(LDAPDelete):
- __doc__ = _('Delete service delegation.')
-
- msg_summary = _('Deleted service delegation "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- if keys[0] in PROTECTED_CONSTRAINT_RULES:
- raise errors.ProtectedEntryError(
- label=_(u'service delegation rule'),
- key=keys[0],
- reason=_(u'privileged service delegation rule')
- )
- return dn
-
-
-@register()
-class servicedelegationrule_find(LDAPSearch):
- __doc__ = _('Search for service delegations rule.')
-
- has_output_params = LDAPSearch.has_output_params + output_params
-
- msg_summary = ngettext(
- '%(count)d service delegation rule matched',
- '%(count)d service delegation rules matched', 0
- )
-
-
-@register()
-class servicedelegationrule_show(LDAPRetrieve):
- __doc__ = _('Display information about a named service delegation rule.')
-
- has_output_params = LDAPRetrieve.has_output_params + output_params
-
-
-@register()
-class servicedelegationrule_add_member(servicedelegation_add_member):
- __doc__ = _('Add member to a named service delegation rule.')
-
- member_names = {
- 'memberprincipal': 'principal',
- }
-
-
-@register()
-class servicedelegationrule_remove_member(servicedelegation_remove_member):
- __doc__ = _('Remove member from a named service delegation rule.')
- member_names = {
- 'memberprincipal': 'principal',
- }
-
-
-@register()
-class servicedelegationrule_add_target(LDAPAddMember):
- __doc__ = _('Add target to a named service delegation rule.')
-
- member_attributes = ['ipaallowedtarget']
- attribute_members = {
- 'ipaallowedtarget': ['servicedelegationtarget'],
- }
- has_output_params = LDAPAddMember.has_output_params + output_params
-
-
-@register()
-class servicedelegationrule_remove_target(LDAPRemoveMember):
- __doc__ = _('Remove target from a named service delegation rule.')
- member_attributes = ['ipaallowedtarget']
- attribute_members = {
- 'ipaallowedtarget': ['servicedelegationtarget'],
- }
- has_output_params = LDAPRemoveMember.has_output_params + output_params
-
-
-@register()
-class servicedelegationtarget(servicedelegation):
- object_name = _('service delegation target')
- object_name_plural = _('service delegation targets')
- object_class = ['groupofprincipals', 'top']
- default_attributes = [
- 'cn', 'memberprincipal',
- ]
- attribute_members = {}
-
- label = _('Service delegation targets')
- label_singular = _('Service delegation target')
-
-
-@register()
-class servicedelegationtarget_add(LDAPCreate):
- __doc__ = _('Create a new service delegation target.')
-
- msg_summary = _('Added service delegation target "%(value)s"')
-
-
-@register()
-class servicedelegationtarget_del(LDAPDelete):
- __doc__ = _('Delete service delegation target.')
-
- msg_summary = _('Deleted service delegation target "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- if keys[0] in PROTECTED_CONSTRAINT_TARGETS:
- raise errors.ProtectedEntryError(
- label=_(u'service delegation target'),
- key=keys[0],
- reason=_(u'privileged service delegation target')
- )
- return dn
-
-
-@register()
-class servicedelegationtarget_find(LDAPSearch):
- __doc__ = _('Search for service delegation target.')
-
- has_output_params = LDAPSearch.has_output_params + output_params
-
- msg_summary = ngettext(
- '%(count)d service delegation target matched',
- '%(count)d service delegation targets matched', 0
- )
-
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope,
- term=None, **options):
- """
- Exclude rules from the search output. A target contains a subset
- of a rule objectclass.
- """
- search_kw = self.args_options_2_entry(**options)
- search_kw['objectclass'] = self.obj.object_class
- attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
- rule_kw = {'objectclass': 'ipakrb5delegationacl'}
- target_filter = ldap.make_filter(rule_kw, rules=ldap.MATCH_NONE)
- attr_filter = ldap.combine_filters(
- (target_filter, attr_filter), rules=ldap.MATCH_ALL
- )
-
- search_kw = {}
- for a in self.obj.default_attributes:
- search_kw[a] = term
-
- term_filter = ldap.make_filter(search_kw, exact=False)
-
- sfilter = ldap.combine_filters(
- (term_filter, attr_filter), rules=ldap.MATCH_ALL
- )
- return sfilter, base_dn, ldap.SCOPE_ONELEVEL
-
-
-@register()
-class servicedelegationtarget_show(LDAPRetrieve):
- __doc__ = _('Display information about a named service delegation target.')
-
- has_output_params = LDAPRetrieve.has_output_params + output_params
-
-
-@register()
-class servicedelegationtarget_add_member(servicedelegation_add_member):
- __doc__ = _('Add member to a named service delegation target.')
-
- member_names = {
- 'memberprincipal': 'principal',
- }
-
-
-@register()
-class servicedelegationtarget_remove_member(servicedelegation_remove_member):
- __doc__ = _('Remove member from a named service delegation target.')
- member_names = {
- 'memberprincipal': 'principal',
- }
diff --git a/ipalib/plugins/session.py b/ipalib/plugins/session.py
deleted file mode 100644
index b03b6b410..000000000
--- a/ipalib/plugins/session.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-from ipalib import api, Command
-from ipalib.request import context
-from ipalib.plugable import Registry
-
-if api.env.in_server:
- from ipalib.session import session_mgr
-
-register = Registry()
-
-
-@register()
-class session_logout(Command):
- '''
- RPC command used to log the current user out of their session.
- '''
- NO_CLI = True
-
- def execute(self, *args, **options):
- session_data = getattr(context, 'session_data', None)
- if session_data is None:
- self.debug('session logout command: no session_data found')
- else:
- session_id = session_data.get('session_id')
- self.debug('session logout command: session_id=%s', session_id)
-
- # Notifiy registered listeners
- session_mgr.auth_mgr.logout(session_data)
-
- return dict(result=None)
diff --git a/ipalib/plugins/stageuser.py b/ipalib/plugins/stageuser.py
deleted file mode 100644
index 86b1935f3..000000000
--- a/ipalib/plugins/stageuser.py
+++ /dev/null
@@ -1,745 +0,0 @@
-# Authors:
-# Thierry Bordaz <tbordaz@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 posixpath
-from copy import deepcopy
-
-import six
-
-from ipalib import api, errors
-from ipalib import Bool
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPCreate,
- LDAPQuery,
- DN)
-from . import baseldap
-from .baseuser import (
- baseuser,
- baseuser_add,
- baseuser_del,
- baseuser_mod,
- baseuser_find,
- baseuser_show,
- NO_UPG_MAGIC,
- baseuser_pwdchars,
- baseuser_output_params,
- status_baseuser_output_params,
- baseuser_add_manager,
- baseuser_remove_manager)
-from ipalib.request import context
-from ipalib import _, ngettext
-from ipalib import output
-from ipaplatform.paths import paths
-from ipapython.ipautil import ipa_generate_password
-from ipalib.capabilities import client_has_capability
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Stageusers
-
-Manage stage user entries.
-
-Stage user entries are directly under the container: "cn=stage users,
-cn=accounts, cn=provisioning, SUFFIX".
-Users can not authenticate with those entries (even if the entries
-contain credentials). Those entries are only candidate to become Active entries.
-
-Active user entries are Posix users directly under the container: "cn=accounts, SUFFIX".
-Users can authenticate with Active entries, at the condition they have
-credentials.
-
-Deleted user entries are Posix users directly under the container: "cn=deleted users,
-cn=accounts, cn=provisioning, SUFFIX".
-Users can not authenticate with those entries, even if the entries contain credentials.
-
-The stage user container contains entries:
- - created by 'stageuser-add' commands that are Posix users,
- - created by external provisioning system.
-
-A valid stage user entry MUST have:
- - entry RDN is 'uid',
- - ipaUniqueID is 'autogenerate'.
-
-IPA supports a wide range of username formats, but you need to be aware of any
-restrictions that may apply to your particular environment. For example,
-usernames that start with a digit or usernames that exceed a certain length
-may cause problems for some UNIX systems.
-Use 'ipa config-mod' to change the username format allowed by IPA tools.
-
-
-EXAMPLES:
-
- Add a new stageuser:
- ipa stageuser-add --first=Tim --last=User --password tuser1
-
- Add a stageuser from the deleted users container:
- ipa stageuser-add --first=Tim --last=User --from-delete tuser1
-
-""")
-
-register = Registry()
-
-
-stageuser_output_params = baseuser_output_params
-
-status_output_params = status_baseuser_output_params
-
-@register()
-class stageuser(baseuser):
- """
- Stage User object
- A Stage user is not an Active user and can not be used to bind with.
- Stage container is: cn=staged users,cn=accounts,cn=provisioning,SUFFIX
- Stage entry conforms the schema
- Stage entry RDN attribute is 'uid'
- Stage entry are disabled (nsAccountLock: True) through cos
- """
-
- container_dn = baseuser.stage_container_dn
- label = _('Stage Users')
- label_singular = _('Stage User')
- object_name = _('stage user')
- object_name_plural = _('stage users')
- managed_permissions = {
- #
- # Stage container
- #
- # Allowed to create stage user
- 'System: Add Stage User': {
- 'ipapermlocation': DN(baseuser.stage_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.stage_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'add'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators', 'Stage User Provisioning'},
- },
- # Allow to read kerberos/password
- 'System: Read Stage User password': {
- 'ipapermlocation': DN(baseuser.stage_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.stage_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'userPassword', 'krbPrincipalKey',
- },
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to update stage user
- 'System: Modify Stage User': {
- 'ipapermlocation': DN(baseuser.stage_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.stage_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to delete stage user
- 'System: Remove Stage User': {
- 'ipapermlocation': DN(baseuser.stage_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.stage_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'delete'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to read any attributes of stage users
- 'System: Read Stage Users': {
- 'ipapermlocation': DN(baseuser.stage_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.stage_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- #
- # Preserve container
- #
- # Allow to read Preserved User
- 'System: Read Preserved Users': {
- 'ipapermlocation': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=posixaccount)'},
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to update Preserved User
- 'System: Modify Preserved Users': {
- 'ipapermlocation': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=posixaccount)'},
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to reset Preserved User password
- 'System: Reset Preserved User password': {
- 'ipapermlocation': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=posixaccount)'},
- 'ipapermright': {'read', 'search', 'write'},
- 'ipapermdefaultattr': {
- 'userPassword', 'krbPrincipalKey','krbPasswordExpiration','krbLastPwdChange'
- },
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to delete preserved user
- 'System: Remove preserved User': {
- 'ipapermlocation': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=*)'},
- 'ipapermright': {'delete'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'Stage User Administrators'},
- },
- #
- # Active container
- #
- # Stage user administrators need write right on RDN when
- # the active user is deleted (preserved)
- 'System: Modify User RDN': {
- 'ipapermlocation': DN(baseuser.active_container_dn, api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtarget': DN('uid=*', baseuser.active_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=posixaccount)'},
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'uid'},
- 'default_privileges': {'Stage User Administrators'},
- },
- #
- # Cross containers autorization
- #
- # Allow to move active user to preserve container (user-del --preserve)
- # Note: targetfilter is the target parent container
- 'System: Preserve User': {
- 'ipapermlocation': DN(api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtargetfrom': DN(baseuser.active_container_dn, api.env.basedn),
- 'ipapermtargetto': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=nsContainer)'},
- 'ipapermright': {'moddn'},
- 'default_privileges': {'Stage User Administrators'},
- },
- # Allow to move preserved user to active container (user-undel)
- # Note: targetfilter is the target parent container
- 'System: Undelete User': {
- 'ipapermlocation': DN(api.env.basedn),
- 'ipapermbindruletype': 'permission',
- 'ipapermtargetfrom': DN(baseuser.delete_container_dn, api.env.basedn),
- 'ipapermtargetto': DN(baseuser.active_container_dn, api.env.basedn),
- 'ipapermtargetfilter': {'(objectclass=nsContainer)'},
- 'ipapermright': {'moddn'},
- 'default_privileges': {'Stage User Administrators'},
- },
- }
-
-@register()
-class stageuser_add(baseuser_add):
- __doc__ = _('Add a new stage user.')
-
- msg_summary = _('Added stage user "%(value)s"')
-
- has_output_params = baseuser_add.has_output_params + stageuser_output_params
-
- takes_options = LDAPCreate.takes_options + (
- Bool(
- 'from_delete?',
- deprecated=True,
- doc=_('Create Stage user in from a delete user'),
- cli_name='from_delete',
- flags={'no_option'},
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- # then givenname and sn are required attributes
- if 'givenname' not in entry_attrs:
- raise errors.RequirementError(name='givenname', error=_('givenname is required'))
-
- if 'sn' not in entry_attrs:
- raise errors.RequirementError(name='sn', error=_('sn is required'))
-
- # we don't want an user private group to be created for this user
- # add NO_UPG_MAGIC description attribute to let the DS plugin know
- entry_attrs.setdefault('description', [])
- entry_attrs['description'].append(NO_UPG_MAGIC)
-
- # uidNumber/gidNumber
- entry_attrs.setdefault('uidnumber', baseldap.DNA_MAGIC)
- entry_attrs.setdefault('gidnumber', baseldap.DNA_MAGIC)
-
- if not client_has_capability(
- options['version'], 'optional_uid_params'):
- # https://fedorahosted.org/freeipa/ticket/2886
- # Old clients say 999 (OLD_DNA_MAGIC) when they really mean
- # "assign a value dynamically".
- OLD_DNA_MAGIC = 999
- if entry_attrs.get('uidnumber') == OLD_DNA_MAGIC:
- entry_attrs['uidnumber'] = baseldap.DNA_MAGIC
- if entry_attrs.get('gidnumber') == OLD_DNA_MAGIC:
- entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
-
-
- # Check the lenght of the RDN (uid) value
- config = ldap.get_ipa_config()
- if 'ipamaxusernamelength' in config:
- if len(keys[-1]) > int(config.get('ipamaxusernamelength')[0]):
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('can be at most %(len)d characters') % dict(
- len = int(config.get('ipamaxusernamelength')[0])
- )
- )
- default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
- entry_attrs.setdefault('loginshell', default_shell)
- # hack so we can request separate first and last name in CLI
- full_name = '%s %s' % (entry_attrs['givenname'], entry_attrs['sn'])
- entry_attrs.setdefault('cn', full_name)
-
- # Homedirectory
- # (order is : option, placeholder (TBD), CLI default value (here in config))
- if 'homedirectory' not in entry_attrs:
- # get home's root directory from config
- homes_root = config.get('ipahomesrootdir', [paths.HOME_DIR])[0]
- # build user's home directory based on his uid
- entry_attrs['homedirectory'] = posixpath.join(homes_root, keys[-1])
-
- # Kerberos principal
- entry_attrs.setdefault('krbprincipalname', '%s@%s' % (entry_attrs['uid'], api.env.realm))
-
-
- # If requested, generate a userpassword
- if 'userpassword' not in entry_attrs and options.get('random'):
- entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
- # save the password so it can be displayed in post_callback
- setattr(context, 'randompassword', entry_attrs['userpassword'])
-
- # Check the email or create it
- if 'mail' in entry_attrs:
- entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'], config)
- else:
- # No e-mail passed in. If we have a default e-mail domain set
- # then we'll add it automatically.
- defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
- if defaultdomain:
- entry_attrs['mail'] = self.obj.normalize_and_validate_email(keys[-1], config)
-
- # If the manager is defined, check it is a ACTIVE user to validate it
- if 'manager' in entry_attrs:
- entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], self.obj.active_container_dn)
-
- if ('objectclass' in entry_attrs
- and 'userclass' in entry_attrs
- and 'ipauser' not in entry_attrs['objectclass']):
- entry_attrs['objectclass'].append('ipauser')
-
- if 'ipatokenradiusconfiglink' in entry_attrs:
- cl = entry_attrs['ipatokenradiusconfiglink']
- if cl:
- if 'objectclass' not in entry_attrs:
- _entry = ldap.get_entry(dn, ['objectclass'])
- entry_attrs['objectclass'] = _entry['objectclass']
-
- if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
-
- answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
- entry_attrs['ipatokenradiusconfiglink'] = answer
-
- self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
- **options)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- config = ldap.get_ipa_config()
-
- # Fetch the entry again to update memberof, mep data, etc updated
- # at the end of the transaction.
- newentry = ldap.get_entry(dn, ['*'])
- entry_attrs.update(newentry)
-
- if options.get('random', False):
- try:
- entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
- except AttributeError:
- # if both randompassword and userpassword options were used
- pass
-
- self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
- return dn
-
-@register()
-class stageuser_del(baseuser_del):
- __doc__ = _('Delete a stage user.')
-
- msg_summary = _('Deleted stage user "%(value)s"')
-
-@register()
-class stageuser_mod(baseuser_mod):
- __doc__ = _('Modify a stage user.')
-
- msg_summary = _('Modified stage user "%(value)s"')
-
- has_output_params = baseuser_mod.has_output_params + stageuser_output_params
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
- **options)
- # Make sure it is not possible to authenticate with a Stage user account
- if 'nsaccountlock' in entry_attrs:
- del entry_attrs['nsaccountlock']
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.post_common_callback(ldap, dn, entry_attrs, **options)
- if 'nsaccountlock' in entry_attrs:
- del entry_attrs['nsaccountlock']
- return dn
-
-@register()
-class stageuser_find(baseuser_find):
- __doc__ = _('Search for stage users.')
-
- member_attributes = ['memberof']
- has_output_params = baseuser_find.has_output_params + stageuser_output_params
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options):
- assert isinstance(base_dn, DN)
- self.pre_common_callback(ldap, filter, attrs_list, base_dn, scope,
- *keys, **options)
-
- container_filter = "(objectclass=posixaccount)"
- # provisioning system can create non posixaccount stage user
- # but then they have to create inetOrgPerson stage user
- stagefilter = filter.replace(container_filter,
- "(|%s(objectclass=inetOrgPerson))" % container_filter)
- self.log.debug("stageuser_find: pre_callback new filter=%s " % (stagefilter))
- return (stagefilter, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- self.post_common_callback(ldap, entries, lockout=True, **options)
- return truncated
-
- msg_summary = ngettext(
- '%(count)d user matched', '%(count)d users matched', 0
- )
-
-@register()
-class stageuser_show(baseuser_show):
- __doc__ = _('Display information about a stage user.')
-
- has_output_params = baseuser_show.has_output_params + stageuser_output_params
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- self.pre_common_callback(ldap, dn, attrs_list, *keys, **options)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- entry_attrs['nsaccountlock'] = True
- self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
- return dn
-
-
-@register()
-class stageuser_activate(LDAPQuery):
- __doc__ = _('Activate a stage user.')
-
- msg_summary = _('Activate a stage user "%(value)s"')
-
- preserved_DN_syntax_attrs = ('manager', 'managedby', 'secretary')
-
- searched_operational_attributes = ['uidNumber', 'gidNumber', 'nsAccountLock', 'ipauniqueid']
-
- has_output = output.standard_entry
- has_output_params = LDAPQuery.has_output_params + stageuser_output_params
-
- def _check_validy(self, dn, entry):
- if dn[0].attr != 'uid':
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('Entry RDN is not \'uid\''),
- )
- for attr in ('cn', 'sn', 'uid'):
- if attr not in entry:
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('Entry has no \'%(attribute)s\'') % dict(attribute=attr),
- )
-
- def _build_new_entry(self, ldap, dn, entry_from, entry_to):
- config = ldap.get_ipa_config()
-
- if 'uidnumber' not in entry_from:
- entry_to['uidnumber'] = baseldap.DNA_MAGIC
- if 'gidnumber' not in entry_from:
- entry_to['gidnumber'] = baseldap.DNA_MAGIC
- if 'homedirectory' not in entry_from:
- # get home's root directory from config
- homes_root = config.get('ipahomesrootdir', [paths.HOME_DIR])[0]
- # build user's home directory based on his uid
- entry_to['homedirectory'] = posixpath.join(homes_root, dn[0].value)
- if 'ipamaxusernamelength' in config:
- if len(dn[0].value) > int(config.get('ipamaxusernamelength')[0]):
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('can be at most %(len)d characters') % dict(
- len = int(config.get('ipamaxusernamelength')[0])
- )
- )
- if 'loginshell' not in entry_from:
- default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
- if default_shell:
- entry_to.setdefault('loginshell', default_shell)
-
- if 'givenname' not in entry_from:
- entry_to['givenname'] = entry_from['cn'][0].split()[0]
-
- if 'krbprincipalname' not in entry_from:
- entry_to['krbprincipalname'] = '%s@%s' % (entry_from['uid'][0], api.env.realm)
-
- def __dict_new_entry(self, *args, **options):
- ldap = self.obj.backend
-
- entry_attrs = self.args_options_2_entry(*args, **options)
- entry_attrs = ldap.make_entry(DN(), entry_attrs)
-
- self.process_attr_options(entry_attrs, None, args, options)
-
- entry_attrs['objectclass'] = deepcopy(self.obj.object_class)
-
- if self.obj.object_class_config:
- config = ldap.get_ipa_config()
- entry_attrs['objectclass'] = config.get(
- self.obj.object_class_config, entry_attrs['objectclass']
- )
-
- return(entry_attrs)
-
- def __merge_values(self, args, options, entry_from, entry_to, attr):
- '''
- This routine merges the values of attr taken from entry_from, into entry_to.
- If attr is a syntax DN attribute, it is replaced by an empty value. It is a preferable solution
- compare to skiping it because the final entry may no longer conform the schema.
- An exception of this is for a limited set of syntax DN attribute that we want to
- preserved (defined in preserved_DN_syntax_attrs)
- see http://www.freeipa.org/page/V3/User_Life-Cycle_Management#Adjustment_of_DN_syntax_attributes
- '''
- if not attr in entry_to:
- if isinstance(entry_from[attr], (list, tuple)):
- # attr is multi value attribute
- entry_to[attr] = []
- else:
- # attr single valued attribute
- entry_to[attr] = None
-
- # At this point entry_to contains for all resulting attributes
- # either a list (possibly empty) or a value (possibly None)
-
- for value in entry_from[attr]:
- # merge all the values from->to
- v = self.__value_2_add(args, options, attr, value)
- if (isinstance(v, str) and v in ('', None)) or \
- (isinstance(v, unicode) and v in (u'', None)):
- try:
- v.decode('utf-8')
- self.log.debug("merge: %s:%r wiped" % (attr, v))
- except Exception:
- self.log.debug("merge %s: [no_print %s]" % (attr, v.__class__.__name__))
- if isinstance(entry_to[attr], (list, tuple)):
- # multi value attribute
- if v not in entry_to[attr]:
- # it may has been added before in the loop
- # so add it only if it not present
- entry_to[attr].append(v)
- else:
- # single value attribute
- # keep the value defined in staging
- entry_to[attr] = v
- else:
- try:
- v.decode('utf-8')
- self.log.debug("Add: %s:%r" % (attr, v))
- except Exception:
- self.log.debug("Add %s: [no_print %s]" % (attr, v.__class__.__name__))
-
- if isinstance(entry_to[attr], (list, tuple)):
- # multi value attribute
- if attr.lower() == 'objectclass':
- entry_to[attr] = [oc.lower() for oc in entry_to[attr]]
- value = value.lower()
- if value not in entry_to[attr]:
- entry_to[attr].append(value)
- else:
- if value not in entry_to[attr]:
- entry_to[attr].append(value)
- else:
- # single value attribute
- if value:
- entry_to[attr] = value
-
- def __value_2_add(self, args, options, attr, value):
- '''
- If the attribute is NOT syntax DN it returns its value.
- Else it checks if the value can be preserved.
- To be preserved:
- - attribute must be in preserved_DN_syntax_attrs
- - value must be an active user DN (in Active container)
- - the active user entry exists
- '''
- ldap = self.obj.backend
-
- if ldap.has_dn_syntax(attr):
- if attr.lower() in self.preserved_DN_syntax_attrs:
- # we are about to add a DN syntax value
- # Check this is a valid DN
- if not isinstance(value, DN):
- return u''
-
- if not self.obj.active_user(value):
- return u''
-
- # Check that this value is a Active user
- try:
- entry_attrs = self._exc_wrapper(args, options, ldap.get_entry)(value, ['dn'])
- return value
- except errors.NotFound:
- return u''
- else:
- return u''
- else:
- return value
-
- def execute(self, *args, **options):
-
- ldap = self.obj.backend
-
- staging_dn = self.obj.get_dn(*args, **options)
- assert isinstance(staging_dn, DN)
-
- # retrieve the current entry
- try:
- entry_attrs = self._exc_wrapper(args, options, ldap.get_entry)(
- staging_dn, ['*']
- )
- except errors.NotFound:
- self.obj.handle_not_found(*args)
- entry_attrs = dict((k.lower(), v) for (k, v) in entry_attrs.items())
-
- # Check it does not exist an active entry with the same RDN
- active_dn = DN(staging_dn[0], api.env.container_user, api.env.basedn)
- try:
- test_entry_attrs = self._exc_wrapper(args, options, ldap.get_entry)(
- active_dn, ['dn']
- )
- assert isinstance(staging_dn, DN)
- raise errors.DuplicateEntry(
- message=_('active user with name "%(user)s" already exists') %
- dict(user=args[-1]))
- except errors.NotFound:
- pass
-
- # Check the original entry is valid
- self._check_validy(staging_dn, entry_attrs)
-
- # Time to build the new entry
- result_entry = {'dn' : active_dn}
- new_entry_attrs = self.__dict_new_entry()
- for (attr, values) in entry_attrs.items():
- self.__merge_values(args, options, entry_attrs, new_entry_attrs, attr)
- result_entry[attr] = values
-
- # Allow Managed entry plugin to do its work
- if 'description' in new_entry_attrs and NO_UPG_MAGIC in new_entry_attrs['description']:
- new_entry_attrs['description'].remove(NO_UPG_MAGIC)
- if result_entry['description'] == NO_UPG_MAGIC:
- del result_entry['description']
-
- for (k, v) in new_entry_attrs.items():
- self.log.debug("new entry: k=%r and v=%r)" % (k, v))
-
- self._build_new_entry(ldap, staging_dn, entry_attrs, new_entry_attrs)
-
- # Add the Active entry
- entry = ldap.make_entry(active_dn, new_entry_attrs)
- self._exc_wrapper(args, options, ldap.add_entry)(entry)
-
- # Now delete the Staging entry
- try:
- self._exc_wrapper(args, options, ldap.delete_entry)(staging_dn)
- except:
- try:
- self.log.error("Fail to delete the Staging user after activating it %s " % (staging_dn))
- self._exc_wrapper(args, options, ldap.delete_entry)(active_dn)
- except Exception:
- self.log.error("Fail to cleanup activation. The user remains active %s" % (active_dn))
- raise
-
- # add the user we just created into the default primary group
- config = ldap.get_ipa_config()
- def_primary_group = config.get('ipadefaultprimarygroup')
- group_dn = self.api.Object['group'].get_dn(def_primary_group)
-
- # if the user is already a member of default primary group,
- # do not raise error
- # this can happen if automember rule or default group is set
- try:
- ldap.add_entry_to_group(active_dn, group_dn)
- except errors.AlreadyGroupMember:
- pass
-
- # Now retrieve the activated entry
- result = self.api.Command.user_show(
- args[-1],
- all=options.get('all', False),
- raw=options.get('raw', False),
- version=options.get('version'),
- )
- result['summary'] = unicode(
- _('Stage user %s activated' % staging_dn[0].value))
-
- return result
-
-
-@register()
-class stageuser_add_manager(baseuser_add_manager):
- __doc__ = _("Add a manager to the stage user entry")
-
-
-@register()
-class stageuser_remove_manager(baseuser_remove_manager):
- __doc__ = _("Remove a manager to the stage user entry")
diff --git a/ipalib/plugins/sudo.py b/ipalib/plugins/sudo.py
deleted file mode 100644
index eb1f49ff9..000000000
--- a/ipalib/plugins/sudo.py
+++ /dev/null
@@ -1,7 +0,0 @@
-#
-# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
-#
-
-from ipalib.text import _
-
-__doc__ = _('commands for controlling sudo configuration')
diff --git a/ipalib/plugins/sudocmd.py b/ipalib/plugins/sudocmd.py
deleted file mode 100644
index e3ae33a84..000000000
--- a/ipalib/plugins/sudocmd.py
+++ /dev/null
@@ -1,203 +0,0 @@
-# Authors:
-# Jr Aquino <jr.aquino@citrixonline.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 ipalib import api, errors
-from ipalib import Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve)
-from ipalib import _, ngettext
-from ipapython.dn import DN
-
-__doc__ = _("""
-Sudo Commands
-
-Commands used as building blocks for sudo
-
-EXAMPLES:
-
- Create a new command
- ipa sudocmd-add --desc='For reading log files' /usr/bin/less
-
- Remove a command
- ipa sudocmd-del /usr/bin/less
-
-""")
-
-register = Registry()
-
-topic = 'sudo'
-
-@register()
-class sudocmd(LDAPObject):
- """
- Sudo Command object.
- """
- container_dn = api.env.container_sudocmd
- object_name = _('sudo command')
- object_name_plural = _('sudo commands')
- object_class = ['ipaobject', 'ipasudocmd']
- permission_filter_objectclasses = ['ipasudocmd']
- # object_class_config = 'ipahostobjectclasses'
- search_attributes = [
- 'sudocmd', 'description',
- ]
- default_attributes = [
- 'sudocmd', 'description', 'memberof',
- ]
- attribute_members = {
- 'memberof': ['sudocmdgroup'],
- }
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- managed_permissions = {
- 'System: Read Sudo Commands': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'description', 'ipauniqueid', 'memberof', 'objectclass',
- 'sudocmd',
- },
- },
- 'System: Add Sudo Command': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///sudocmd=*,cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo command";allow (add) groupdn = "ldap:///cn=Add Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(objectclass=ipasudocmd)")(target = "ldap:///cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo command";allow (add) groupdn = "ldap:///cn=Add Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Delete Sudo Command': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///sudocmd=*,cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo command";allow (delete) groupdn = "ldap:///cn=Delete Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(objectclass=ipasudocmd)")(target = "ldap:///cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo command";allow (delete) groupdn = "ldap:///cn=Delete Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Modify Sudo Command': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'description'},
- 'replaces': [
- '(targetattr = "description")(target = "ldap:///sudocmd=*,cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Modify Sudo command";allow (write) groupdn = "ldap:///cn=Modify Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(objectclass=ipasudocmd)")(targetattr = "description")(target = "ldap:///cn=sudocmds,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Modify Sudo command";allow (write) groupdn = "ldap:///cn=Modify Sudo command,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- }
-
- label = _('Sudo Commands')
- label_singular = _('Sudo Command')
-
- takes_params = (
- Str('sudocmd',
- cli_name='command',
- label=_('Sudo Command'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('A description of this command'),
- ),
- )
-
- def get_dn(self, *keys, **options):
- if keys[-1].endswith('.'):
- keys[-1] = keys[-1][:-1]
- dn = super(sudocmd, self).get_dn(*keys, **options)
- try:
- self.backend.get_entry(dn, [''])
- except errors.NotFound:
- try:
- entry_attrs = self.backend.find_entry_by_attr(
- 'sudocmd', keys[-1], self.object_class, [''],
- DN(self.container_dn, api.env.basedn))
- dn = entry_attrs.dn
- except errors.NotFound:
- pass
- return dn
-
-
-@register()
-class sudocmd_add(LDAPCreate):
- __doc__ = _('Create new Sudo Command.')
-
- msg_summary = _('Added Sudo Command "%(value)s"')
-
-
-@register()
-class sudocmd_del(LDAPDelete):
- __doc__ = _('Delete Sudo Command.')
-
- msg_summary = _('Deleted Sudo Command "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- filters = [
- ldap.make_filter_from_attr(attr, dn)
- for attr in ('memberallowcmd', 'memberdenycmd')]
- filter = ldap.combine_filters(filters, ldap.MATCH_ANY)
- filter = ldap.combine_filters(
- (filter, ldap.make_filter_from_attr('objectClass', 'ipasudorule')),
- ldap.MATCH_ALL)
- dependent_sudorules = []
- try:
- entries, truncated = ldap.find_entries(
- filter, ['cn'],
- base_dn=DN(api.env.container_sudorule, api.env.basedn))
- except errors.NotFound:
- pass
- else:
- for entry_attrs in entries:
- [cn] = entry_attrs['cn']
- dependent_sudorules.append(cn)
-
- if dependent_sudorules:
- raise errors.DependentEntry(
- key=keys[0], label='sudorule',
- dependent=', '.join(dependent_sudorules))
- return dn
-
-
-@register()
-class sudocmd_mod(LDAPUpdate):
- __doc__ = _('Modify Sudo Command.')
-
- msg_summary = _('Modified Sudo Command "%(value)s"')
-
-
-@register()
-class sudocmd_find(LDAPSearch):
- __doc__ = _('Search for Sudo Commands.')
-
- msg_summary = ngettext(
- '%(count)d Sudo Command matched', '%(count)d Sudo Commands matched', 0
- )
-
-
-@register()
-class sudocmd_show(LDAPRetrieve):
- __doc__ = _('Display Sudo Command.')
-
diff --git a/ipalib/plugins/sudocmdgroup.py b/ipalib/plugins/sudocmdgroup.py
deleted file mode 100644
index 9e8c016fd..000000000
--- a/ipalib/plugins/sudocmdgroup.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# Authors:
-# Jr Aquino <jr.aquino@citrixonline.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 ipalib import api
-from ipalib import Str
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPAddMember,
- LDAPRemoveMember)
-from ipalib import _, ngettext
-
-__doc__ = _("""
-Groups of Sudo Commands
-
-Manage groups of Sudo Commands.
-
-EXAMPLES:
-
- Add a new Sudo Command Group:
- ipa sudocmdgroup-add --desc='administrators commands' admincmds
-
- Remove a Sudo Command Group:
- ipa sudocmdgroup-del admincmds
-
- Manage Sudo Command Group membership, commands:
- ipa sudocmdgroup-add-member --sudocmds=/usr/bin/less --sudocmds=/usr/bin/vim admincmds
-
- Manage Sudo Command Group membership, commands:
- ipa sudocmdgroup-remove-member --sudocmds=/usr/bin/less admincmds
-
- Show a Sudo Command Group:
- ipa sudocmdgroup-show admincmds
-""")
-
-register = Registry()
-
-topic = 'sudo'
-
-@register()
-class sudocmdgroup(LDAPObject):
- """
- Sudo Command Group object.
- """
- container_dn = api.env.container_sudocmdgroup
- object_name = _('sudo command group')
- object_name_plural = _('sudo command groups')
- object_class = ['ipaobject', 'ipasudocmdgrp']
- permission_filter_objectclasses = ['ipasudocmdgrp']
- default_attributes = [
- 'cn', 'description', 'member',
- ]
- uuid_attribute = 'ipauniqueid'
- attribute_members = {
- 'member': ['sudocmd'],
- }
- managed_permissions = {
- 'System: Read Sudo Command Groups': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'cn', 'description', 'ipauniqueid',
- 'member', 'o', 'objectclass', 'ou', 'owner', 'seealso',
- 'memberuser', 'memberhost',
- },
- },
- 'System: Add Sudo Command Group': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=sudocmdgroups,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo command group";allow (add) groupdn = "ldap:///cn=Add Sudo command group,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Delete Sudo Command Group': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///cn=*,cn=sudocmdgroups,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo command group";allow (delete) groupdn = "ldap:///cn=Delete Sudo command group,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Modify Sudo Command Group': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'description'},
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Manage Sudo Command Group Membership': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=*,cn=sudocmdgroups,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Manage Sudo command group membership";allow (write) groupdn = "ldap:///cn=Manage Sudo command group membership,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- }
-
- label = _('Sudo Command Groups')
- label_singular = _('Sudo Command Group')
-
- takes_params = (
- Str('cn',
- cli_name='sudocmdgroup_name',
- label=_('Sudo Command Group'),
- primary_key=True,
- normalizer=lambda value: value.lower(),
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Group description'),
- ),
- Str('membercmd_sudocmd?',
- label=_('Commands'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('membercmd_sudocmdgroup?',
- label=_('Sudo Command Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- )
-
-
-
-@register()
-class sudocmdgroup_add(LDAPCreate):
- __doc__ = _('Create new Sudo Command Group.')
-
- msg_summary = _('Added Sudo Command Group "%(value)s"')
-
-
-
-@register()
-class sudocmdgroup_del(LDAPDelete):
- __doc__ = _('Delete Sudo Command Group.')
-
- msg_summary = _('Deleted Sudo Command Group "%(value)s"')
-
-
-
-@register()
-class sudocmdgroup_mod(LDAPUpdate):
- __doc__ = _('Modify Sudo Command Group.')
-
- msg_summary = _('Modified Sudo Command Group "%(value)s"')
-
-
-
-@register()
-class sudocmdgroup_find(LDAPSearch):
- __doc__ = _('Search for Sudo Command Groups.')
-
- msg_summary = ngettext(
- '%(count)d Sudo Command Group matched',
- '%(count)d Sudo Command Groups matched', 0
- )
-
-
-
-@register()
-class sudocmdgroup_show(LDAPRetrieve):
- __doc__ = _('Display Sudo Command Group.')
-
-
-
-@register()
-class sudocmdgroup_add_member(LDAPAddMember):
- __doc__ = _('Add members to Sudo Command Group.')
-
-
-
-@register()
-class sudocmdgroup_remove_member(LDAPRemoveMember):
- __doc__ = _('Remove members from Sudo Command Group.')
-
diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py
deleted file mode 100644
index 15d03c659..000000000
--- a/ipalib/plugins/sudorule.py
+++ /dev/null
@@ -1,998 +0,0 @@
-# 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/>.
-
-import netaddr
-import six
-
-from ipalib import api, errors
-from ipalib import Str, StrEnum, Bool, Int
-from ipalib.plugable import Registry
-from .baseldap import (LDAPObject, LDAPCreate, LDAPDelete,
- LDAPUpdate, LDAPSearch, LDAPRetrieve,
- LDAPQuery, LDAPAddMember, LDAPRemoveMember,
- add_external_pre_callback,
- add_external_post_callback,
- remove_external_post_callback,
- output, entry_to_dict, pkey_to_value,
- external_host_param)
-from .hbacrule import is_all
-from ipalib import _, ngettext
-from ipalib.util import validate_hostmask
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Sudo Rules
-""") + _("""
-Sudo (su "do") allows a system administrator to delegate authority to
-give certain users (or groups of users) the ability to run some (or all)
-commands as root or another user while providing an audit trail of the
-commands and their arguments.
-""") + _("""
-FreeIPA provides a means to configure the various aspects of Sudo:
- Users: The user(s)/group(s) allowed to invoke Sudo.
- Hosts: The host(s)/hostgroup(s) which the user is allowed to to invoke Sudo.
- Allow Command: The specific command(s) permitted to be run via Sudo.
- Deny Command: The specific command(s) prohibited to be run via Sudo.
- RunAsUser: The user(s) or group(s) of users whose rights Sudo will be invoked with.
- RunAsGroup: The group(s) whose gid rights Sudo will be invoked with.
- Options: The various Sudoers Options that can modify Sudo's behavior.
-""") + _("""
-An order can be added to a sudorule to control the order in which they
-are evaluated (if the client supports it). This order is an integer and
-must be unique.
-""") + _("""
-FreeIPA provides a designated binddn to use with Sudo located at:
-uid=sudo,cn=sysaccounts,cn=etc,dc=example,dc=com
-""") + _("""
-To enable the binddn run the following command to set the password:
-LDAPTLS_CACERT=/etc/ipa/ca.crt /usr/bin/ldappasswd -S -W \
--h ipa.example.com -ZZ -D "cn=Directory Manager" \
-uid=sudo,cn=sysaccounts,cn=etc,dc=example,dc=com
-""") + _("""
-EXAMPLES:
-""") + _("""
- Create a new rule:
- ipa sudorule-add readfiles
-""") + _("""
- Add sudo command object and add it as allowed command in the rule:
- ipa sudocmd-add /usr/bin/less
- ipa sudorule-add-allow-command readfiles --sudocmds /usr/bin/less
-""") + _("""
- Add a host to the rule:
- ipa sudorule-add-host readfiles --hosts server.example.com
-""") + _("""
- Add a user to the rule:
- ipa sudorule-add-user readfiles --users jsmith
-""") + _("""
- Add a special Sudo rule for default Sudo server configuration:
- ipa sudorule-add defaults
-""") + _("""
- Set a default Sudo option:
- ipa sudorule-add-option defaults --sudooption '!authenticate'
-""")
-
-register = Registry()
-
-topic = 'sudo'
-
-
-def deprecated(attribute):
- raise errors.ValidationError(
- name=attribute,
- error=_('this option has been deprecated.'))
-
-
-hostmask_membership_param = Str('hostmask?', validate_hostmask,
- label=_('host masks of allowed hosts'),
- flags=['no_create', 'no_update', 'no_search'],
- multivalue=True,
- )
-
-def validate_externaluser(ugettext, value):
- deprecated('externaluser')
-
-
-def validate_runasextuser(ugettext, value):
- deprecated('runasexternaluser')
-
-
-def validate_runasextgroup(ugettext, value):
- deprecated('runasexternalgroup')
-
-
-@register()
-class sudorule(LDAPObject):
- """
- Sudo Rule object.
- """
- container_dn = api.env.container_sudorule
- object_name = _('sudo rule')
- object_name_plural = _('sudo rules')
- object_class = ['ipaassociation', 'ipasudorule']
- permission_filter_objectclasses = ['ipasudorule']
- default_attributes = [
- 'cn', 'ipaenabledflag', 'externaluser',
- 'description', 'usercategory', 'hostcategory',
- 'cmdcategory', 'memberuser', 'memberhost',
- 'memberallowcmd', 'memberdenycmd', 'ipasudoopt',
- 'ipasudorunas', 'ipasudorunasgroup',
- 'ipasudorunasusercategory', 'ipasudorunasgroupcategory',
- 'sudoorder', 'hostmask', 'externalhost', 'ipasudorunasextusergroup',
- 'ipasudorunasextgroup', 'ipasudorunasextuser'
- ]
- uuid_attribute = 'ipauniqueid'
- rdn_attribute = 'ipauniqueid'
- attribute_members = {
- 'memberuser': ['user', 'group'],
- 'memberhost': ['host', 'hostgroup'],
- 'memberallowcmd': ['sudocmd', 'sudocmdgroup'],
- 'memberdenycmd': ['sudocmd', 'sudocmdgroup'],
- 'ipasudorunas': ['user', 'group'],
- 'ipasudorunasgroup': ['group'],
- }
- managed_permissions = {
- 'System: Read Sudo Rules': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cmdcategory', 'cn', 'description', 'externalhost',
- 'externaluser', 'hostcategory', 'hostmask', 'ipaenabledflag',
- 'ipasudoopt', 'ipasudorunas', 'ipasudorunasextgroup',
- 'ipasudorunasextuser', 'ipasudorunasextusergroup',
- 'ipasudorunasgroup',
- 'ipasudorunasgroupcategory', 'ipasudorunasusercategory',
- 'ipauniqueid', 'memberallowcmd', 'memberdenycmd',
- 'memberhost', 'memberuser', 'sudonotafter', 'sudonotbefore',
- 'sudoorder', 'usercategory', 'objectclass', 'member',
- },
- },
- 'System: Read Sudoers compat tree': {
- 'non_object': True,
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('ou=sudoers', api.env.basedn),
- 'ipapermbindruletype': 'anonymous',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'ou',
- 'sudouser', 'sudohost', 'sudocommand', 'sudorunas',
- 'sudorunasuser', 'sudorunasgroup', 'sudooption',
- 'sudonotbefore', 'sudonotafter', 'sudoorder', 'description',
- },
- },
- 'System: Add Sudo rule': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo rule";allow (add) groupdn = "ldap:///cn=Add Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Delete Sudo rule': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo rule";allow (delete) groupdn = "ldap:///cn=Delete Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- 'System: Modify Sudo rule': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'description', 'ipaenabledflag', 'usercategory',
- 'hostcategory', 'cmdcategory', 'ipasudorunasusercategory',
- 'ipasudorunasgroupcategory', 'externaluser',
- 'ipasudorunasextusergroup',
- 'ipasudorunasextuser', 'ipasudorunasextgroup', 'memberdenycmd',
- 'memberallowcmd', 'memberuser', 'memberhost', 'externalhost',
- 'sudonotafter', 'hostmask', 'sudoorder', 'sudonotbefore',
- 'ipasudorunas', 'externalhost', 'ipasudorunasgroup',
- 'ipasudoopt', 'memberhost',
- },
- 'replaces': [
- '(targetattr = "description || ipaenabledflag || usercategory || hostcategory || cmdcategory || ipasudorunasusercategory || ipasudorunasgroupcategory || externaluser || ipasudorunasextuser || ipasudorunasextgroup || memberdenycmd || memberallowcmd || memberuser")(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Modify Sudo rule";allow (write) groupdn = "ldap:///cn=Modify Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'Sudo Administrator'},
- },
- }
-
- label = _('Sudo Rules')
- label_singular = _('Sudo Rule')
-
- takes_params = (
- Str('cn',
- cli_name='sudorule_name',
- label=_('Rule name'),
- primary_key=True,
- ),
- Str('description?',
- cli_name='desc',
- label=_('Description'),
- ),
- Bool('ipaenabledflag?',
- label=_('Enabled'),
- flags=['no_option'],
- ),
- StrEnum('usercategory?',
- cli_name='usercat',
- label=_('User category'),
- doc=_('User category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('hostcategory?',
- cli_name='hostcat',
- label=_('Host category'),
- doc=_('Host category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('cmdcategory?',
- cli_name='cmdcat',
- label=_('Command category'),
- doc=_('Command category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('ipasudorunasusercategory?',
- cli_name='runasusercat',
- label=_('RunAs User category'),
- doc=_('RunAs User category the rule applies to'),
- values=(u'all', ),
- ),
- StrEnum('ipasudorunasgroupcategory?',
- cli_name='runasgroupcat',
- label=_('RunAs Group category'),
- doc=_('RunAs Group category the rule applies to'),
- values=(u'all', ),
- ),
- Int('sudoorder?',
- cli_name='order',
- label=_('Sudo order'),
- doc=_('integer to order the Sudo rules'),
- default=0,
- minvalue=0,
- ),
- Str('memberuser_user?',
- label=_('Users'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberuser_group?',
- label=_('User Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('externaluser?', validate_externaluser,
- cli_name='externaluser',
- label=_('External User'),
- doc=_('External User the rule applies to (sudorule-find only)'),
- ),
- Str('memberhost_host?',
- label=_('Hosts'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberhost_hostgroup?',
- label=_('Host Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('hostmask', validate_hostmask,
- normalizer=lambda x: unicode(netaddr.IPNetwork(x).cidr),
- label=_('Host Masks'),
- flags=['no_create', 'no_update', 'no_search'],
- multivalue=True,
- ),
- external_host_param,
- Str('memberallowcmd_sudocmd?',
- label=_('Sudo Allow Commands'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberdenycmd_sudocmd?',
- label=_('Sudo Deny Commands'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberallowcmd_sudocmdgroup?',
- label=_('Sudo Allow Command Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('memberdenycmd_sudocmdgroup?',
- label=_('Sudo Deny Command Groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('ipasudorunas_user?',
- label=_('RunAs Users'),
- doc=_('Run as a user'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('ipasudorunas_group?',
- label=_('Groups of RunAs Users'),
- doc=_('Run as any user within a specified group'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('ipasudorunasextuser?', validate_runasextuser,
- cli_name='runasexternaluser',
- label=_('RunAs External User'),
- doc=_('External User the commands can run as (sudorule-find only)'),
- ),
- Str('ipasudorunasextusergroup?',
- cli_name='runasexternalusergroup',
- label=_('External Groups of RunAs Users'),
- doc=_('External Groups of users that the command can run as'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('ipasudorunasgroup_group?',
- label=_('RunAs Groups'),
- doc=_('Run with the gid of a specified POSIX group'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str('ipasudorunasextgroup?', validate_runasextgroup,
- cli_name='runasexternalgroup',
- label=_('RunAs External Group'),
- doc=_('External Group the commands can run as (sudorule-find only)'),
- ),
- Str('ipasudoopt?',
- label=_('Sudo Option'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- )
-
- order_not_unique_msg = _(
- 'order must be a unique value (%(order)d already used by %(rule)s)'
- )
-
- def check_order_uniqueness(self, *keys, **options):
- if options.get('sudoorder') is not None:
- entries = self.methods.find(
- sudoorder=options['sudoorder']
- )['result']
-
- if len(entries) > 0:
- rule_name = entries[0]['cn'][0]
- raise errors.ValidationError(
- name='order',
- error=self.order_not_unique_msg % {
- 'order': options['sudoorder'],
- 'rule': rule_name,
- }
- )
-
-
-@register()
-class sudorule_add(LDAPCreate):
- __doc__ = _('Create new Sudo Rule.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- self.obj.check_order_uniqueness(*keys, **options)
- # Sudo Rules are enabled by default
- entry_attrs['ipaenabledflag'] = 'TRUE'
- return dn
-
- msg_summary = _('Added Sudo Rule "%(value)s"')
-
-
-@register()
-class sudorule_del(LDAPDelete):
- __doc__ = _('Delete Sudo Rule.')
-
- msg_summary = _('Deleted Sudo Rule "%(value)s"')
-
-
-@register()
-class sudorule_mod(LDAPUpdate):
- __doc__ = _('Modify Sudo Rule.')
-
- msg_summary = _('Modified Sudo Rule "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- if 'sudoorder' in options:
- new_order = options.get('sudoorder')
- old_entry = self.api.Command.sudorule_show(keys[-1])['result']
- if 'sudoorder' in old_entry:
- old_order = int(old_entry['sudoorder'][0])
- if old_order != new_order:
- self.obj.check_order_uniqueness(*keys, **options)
- else:
- self.obj.check_order_uniqueness(*keys, **options)
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- error = _("%(type)s category cannot be set to 'all' "
- "while there are allowed %(objects)s")
-
- category_info = [(
- 'usercategory',
- ['memberuser', 'externaluser'],
- error % {'type': _('user'), 'objects': _('users')}
- ),
- (
- 'hostcategory',
- ['memberhost', 'externalhost', 'hostmask'],
- error % {'type': _('host'), 'objects': _('hosts')}
- ),
- (
- 'cmdcategory',
- ['memberallowcmd'],
- error % {'type': _('command'), 'objects': _('commands')}
- ),
- (
- 'ipasudorunasusercategory',
- ['ipasudorunas', 'ipasudorunasextuser',
- 'ipasudorunasextusergroup'],
- error % {'type': _('runAs user'), 'objects': _('runAs users')}
- ),
- (
- 'ipasudorunasgroupcategory',
- ['ipasudorunasgroup', 'ipasudorunasextgroup'],
- error % {'type': _('group runAs'), 'objects': _('runAs groups')}
- ),
- ]
-
-
- # Enforce the checks for all the categories
- for category, member_attrs, error in category_info:
- any_member_attrs_set = any(attr in _entry_attrs
- for attr in member_attrs)
-
- if is_all(options, category) and any_member_attrs_set:
- raise errors.MutuallyExclusiveError(reason=error)
-
- return dn
-
-
-@register()
-class sudorule_find(LDAPSearch):
- __doc__ = _('Search for Sudo Rule.')
-
- msg_summary = ngettext(
- '%(count)d Sudo Rule matched', '%(count)d Sudo Rules matched', 0
- )
-
-
-@register()
-class sudorule_show(LDAPRetrieve):
- __doc__ = _('Display Sudo Rule.')
-
-
-@register()
-class sudorule_enable(LDAPQuery):
- __doc__ = _('Enable a Sudo Rule.')
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['TRUE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(result=True)
-
-
-@register()
-class sudorule_disable(LDAPQuery):
- __doc__ = _('Disable a Sudo Rule.')
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
- try:
- entry_attrs = ldap.get_entry(dn, ['ipaenabledflag'])
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- entry_attrs['ipaenabledflag'] = ['FALSE']
-
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
-
- return dict(result=True)
-
-
-@register()
-class sudorule_add_allow_command(LDAPAddMember):
- __doc__ = _('Add commands and sudo command groups affected by Sudo Rule.')
-
- member_attributes = ['memberallowcmd']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if is_all(_entry_attrs, 'cmdcategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("commands cannot be added when command "
- "category='all'"))
-
- return dn
-
-
-@register()
-class sudorule_remove_allow_command(LDAPRemoveMember):
- __doc__ = _('Remove commands and sudo command groups affected by Sudo Rule.')
-
- member_attributes = ['memberallowcmd']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
-
-@register()
-class sudorule_add_deny_command(LDAPAddMember):
- __doc__ = _('Add commands and sudo command groups affected by Sudo Rule.')
-
- member_attributes = ['memberdenycmd']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- return dn
-
-
-@register()
-class sudorule_remove_deny_command(LDAPRemoveMember):
- __doc__ = _('Remove commands and sudo command groups affected by Sudo Rule.')
-
- member_attributes = ['memberdenycmd']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
-
-@register()
-class sudorule_add_user(LDAPAddMember):
- __doc__ = _('Add users and groups affected by Sudo Rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if is_all(_entry_attrs, 'usercategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when user category='all'"))
-
- return add_external_pre_callback('user', ldap, dn, keys, options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return add_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberuser',
- membertype='user',
- externalattr='externaluser')
-
-
-@register()
-class sudorule_remove_user(LDAPRemoveMember):
- __doc__ = _('Remove users and groups affected by Sudo Rule.')
-
- member_attributes = ['memberuser']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return remove_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberuser',
- membertype='user',
- externalattr='externaluser')
-
-
-@register()
-class sudorule_add_host(LDAPAddMember):
- __doc__ = _('Add hosts and hostgroups affected by Sudo Rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def get_options(self):
- for option in super(sudorule_add_host, self).get_options():
- yield option
- yield hostmask_membership_param
-
- def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
- assert isinstance(dn, DN)
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if is_all(_entry_attrs, 'hostcategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("hosts cannot be added when host category='all'"))
-
- return add_external_pre_callback('host', ldap, dn, keys, options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if 'hostmask' in options:
- norm = lambda x: unicode(netaddr.IPNetwork(x).cidr)
-
- old_masks = set(norm(m) for m in _entry_attrs.get('hostmask', []))
- new_masks = set(norm(m) for m in options['hostmask'])
-
- num_added = len(new_masks - old_masks)
-
- if num_added:
- entry_attrs['hostmask'] = list(old_masks | new_masks)
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
- completed = completed + num_added
-
- return add_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberhost',
- membertype='host',
- externalattr='externalhost')
-
-
-@register()
-class sudorule_remove_host(LDAPRemoveMember):
- __doc__ = _('Remove hosts and hostgroups affected by Sudo Rule.')
-
- member_attributes = ['memberhost']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
- def get_options(self):
- for option in super(sudorule_remove_host, self).get_options():
- yield option
- yield hostmask_membership_param
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if 'hostmask' in options:
- def norm(x):
- return unicode(netaddr.IPNetwork(x).cidr)
-
- old_masks = set(norm(m) for m in _entry_attrs.get('hostmask', []))
- removed_masks = set(norm(m) for m in options['hostmask'])
-
- num_added = len(removed_masks & old_masks)
-
- if num_added:
- entry_attrs['hostmask'] = list(old_masks - removed_masks)
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
- completed = completed + num_added
-
- return remove_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='memberhost',
- membertype='host',
- externalattr='externalhost')
-
-
-@register()
-class sudorule_add_runasuser(LDAPAddMember):
- __doc__ = _('Add users and groups for Sudo to execute as.')
-
- member_attributes = ['ipasudorunas']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- def check_validity(runas):
- v = unicode(runas)
- if v.upper() == u'ALL':
- return False
- return True
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if any((is_all(_entry_attrs, 'ipasudorunasusercategory'),
- is_all(_entry_attrs, 'ipasudorunasgroupcategory'))):
-
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when runAs user or runAs "
- "group category='all'"))
-
- if 'user' in options:
- for name in options['user']:
- if not check_validity(name):
- raise errors.ValidationError(name='runas-user',
- error=unicode(_("RunAsUser does not accept "
- "'%(name)s' as a user name")) %
- dict(name=name))
-
- if 'group' in options:
- for name in options['group']:
- if not check_validity(name):
- raise errors.ValidationError(name='runas-user',
- error=unicode(_("RunAsUser does not accept "
- "'%(name)s' as a group name")) %
- dict(name=name))
-
- return add_external_pre_callback('user', ldap, dn, keys, options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
-
- # Since external_post_callback returns the total number of completed
- # entries yet (that is, any external users it added plus the value of
- # passed variable 'completed', we need to pass 0 as completed,
- # so that the entries added by the framework are not counted twice
- # (once in each call of add_external_post_callback)
-
- (completed_ex_users, dn) = add_external_post_callback(ldap, dn,
- entry_attrs,
- failed=failed,
- completed=0,
- memberattr='ipasudorunas',
- membertype='user',
- externalattr='ipasudorunasextuser',
- )
-
- (completed_ex_groups, dn) = add_external_post_callback(ldap, dn,
- entry_attrs=entry_attrs,
- failed=failed,
- completed=0,
- memberattr='ipasudorunas',
- membertype='group',
- externalattr='ipasudorunasextusergroup',
- )
-
- return (completed + completed_ex_users + completed_ex_groups, dn)
-
-
-@register()
-class sudorule_remove_runasuser(LDAPRemoveMember):
- __doc__ = _('Remove users and groups for Sudo to execute as.')
-
- member_attributes = ['ipasudorunas']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
-
- # Since external_post_callback returns the total number of completed
- # entries yet (that is, any external users it added plus the value of
- # passed variable 'completed', we need to pass 0 as completed,
- # so that the entries added by the framework are not counted twice
- # (once in each call of remove_external_post_callback)
-
- (completed_ex_users, dn) = remove_external_post_callback(ldap, dn,
- entry_attrs=entry_attrs,
- failed=failed,
- completed=0,
- memberattr='ipasudorunas',
- membertype='user',
- externalattr='ipasudorunasextuser',
- )
-
- (completed_ex_groups, dn) = remove_external_post_callback(ldap, dn,
- entry_attrs=entry_attrs,
- failed=failed,
- completed=0,
- memberattr='ipasudorunas',
- membertype='group',
- externalattr='ipasudorunasextusergroup',
- )
-
- return (completed + completed_ex_users + completed_ex_groups, dn)
-
-
-@register()
-class sudorule_add_runasgroup(LDAPAddMember):
- __doc__ = _('Add group for Sudo to execute as.')
-
- member_attributes = ['ipasudorunasgroup']
- member_count_out = ('%i object added.', '%i objects added.')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- def check_validity(runas):
- v = unicode(runas)
- if v.upper() == u'ALL':
- return False
- return True
-
- try:
- _entry_attrs = ldap.get_entry(dn, self.obj.default_attributes)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if is_all(_entry_attrs, 'ipasudorunasusercategory') or \
- is_all(_entry_attrs, 'ipasudorunasgroupcategory'):
- raise errors.MutuallyExclusiveError(
- reason=_("users cannot be added when runAs user or runAs "
- "group category='all'"))
-
- if 'group' in options:
- for name in options['group']:
- if not check_validity(name):
- raise errors.ValidationError(name='runas-group',
- error=unicode(_("RunAsGroup does not accept "
- "'%(name)s' as a group name")) %
- dict(name=name))
-
- return add_external_pre_callback('group', ldap, dn, keys, options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return add_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='ipasudorunasgroup',
- membertype='group',
- externalattr='ipasudorunasextgroup',
- )
-
-
-@register()
-class sudorule_remove_runasgroup(LDAPRemoveMember):
- __doc__ = _('Remove group for Sudo to execute as.')
-
- member_attributes = ['ipasudorunasgroup']
- member_count_out = ('%i object removed.', '%i objects removed.')
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs,
- *keys, **options):
- assert isinstance(dn, DN)
- return remove_external_post_callback(ldap, dn, entry_attrs,
- failed=failed,
- completed=completed,
- memberattr='ipasudorunasgroup',
- membertype='group',
- externalattr='ipasudorunasextgroup',
- )
-
-
-@register()
-class sudorule_add_option(LDAPQuery):
- __doc__ = _('Add an option to the Sudo Rule.')
-
- has_output = output.standard_entry
- takes_options = (
- Str('ipasudoopt',
- cli_name='sudooption',
- label=_('Sudo Option'),
- ),
- )
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
-
- if not options['ipasudoopt'].strip():
- raise errors.EmptyModlist()
- entry_attrs = ldap.get_entry(dn, ['ipasudoopt'])
-
- try:
- if options['ipasudoopt'] not in entry_attrs['ipasudoopt']:
- entry_attrs.setdefault('ipasudoopt', []).append(
- options['ipasudoopt'])
- else:
- raise errors.DuplicateEntry
- except KeyError:
- entry_attrs.setdefault('ipasudoopt', []).append(
- options['ipasudoopt'])
- try:
- ldap.update_entry(entry_attrs)
- except errors.EmptyModlist:
- pass
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- attrs_list = self.obj.default_attributes
- entry_attrs = ldap.get_entry(dn, attrs_list)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- return dict(result=entry_attrs, value=pkey_to_value(cn, options))
-
-
-@register()
-class sudorule_remove_option(LDAPQuery):
- __doc__ = _('Remove an option from Sudo Rule.')
-
- has_output = output.standard_entry
- takes_options = (
- Str('ipasudoopt',
- cli_name='sudooption',
- label=_('Sudo Option'),
- ),
- )
-
- def execute(self, cn, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_dn(cn)
-
- if not options['ipasudoopt'].strip():
- raise errors.EmptyModlist()
-
- entry_attrs = ldap.get_entry(dn, ['ipasudoopt'])
-
- try:
- if options['ipasudoopt'] in entry_attrs['ipasudoopt']:
- entry_attrs.setdefault('ipasudoopt', []).remove(
- options['ipasudoopt'])
- ldap.update_entry(entry_attrs)
- else:
- raise errors.AttrValueNotFound(
- attr='ipasudoopt',
- value=options['ipasudoopt']
- )
- except ValueError:
- pass
- except KeyError:
- raise errors.AttrValueNotFound(
- attr='ipasudoopt',
- value=options['ipasudoopt']
- )
- except errors.NotFound:
- self.obj.handle_not_found(cn)
-
- attrs_list = self.obj.default_attributes
- entry_attrs = ldap.get_entry(dn, attrs_list)
-
- entry_attrs = entry_to_dict(entry_attrs, **options)
-
- return dict(result=entry_attrs, value=pkey_to_value(cn, options))
diff --git a/ipalib/plugins/topology.py b/ipalib/plugins/topology.py
deleted file mode 100644
index a6e638479..000000000
--- a/ipalib/plugins/topology.py
+++ /dev/null
@@ -1,503 +0,0 @@
-#
-# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
-#
-
-import six
-
-from ipalib import api, errors
-from ipalib import Int, Str, StrEnum, Flag, DNParam
-from ipalib.plugable import Registry
-from .baseldap import (
- LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete, LDAPUpdate, LDAPQuery,
- LDAPRetrieve)
-from ipalib import _, ngettext
-from ipalib import output
-from ipalib.constants import DOMAIN_LEVEL_1
-from ipalib.util import create_topology_graph, get_topology_connection_errors
-from ipapython.dn import DN
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Topology
-
-Management of a replication topology at domain level 1.
-""") + _("""
-IPA server's data is stored in LDAP server in two suffixes:
-* domain suffix, e.g., 'dc=example,dc=com', contains all domain related data
-* ca suffix, 'o=ipaca', is present only on server with CA installed. It
- contains data for Certificate Server component
-""") + _("""
-Data stored on IPA servers is replicated to other IPA servers. The way it is
-replicated is defined by replication agreements. Replication agreements needs
-to be set for both suffixes separately. On domain level 0 they are managed
-using ipa-replica-manage and ipa-csreplica-manage tools. With domain level 1
-they are managed centrally using `ipa topology*` commands.
-""") + _("""
-Agreements are represented by topology segments. By default topology segment
-represents 2 replication agreements - one for each direction, e.g., A to B and
-B to A. Creation of unidirectional segments is not allowed.
-""") + _("""
-To verify that no server is disconnected in the topology of the given suffix,
-use:
- ipa topologysuffix-verify $suffix
-""") + _("""
-
-Examples:
- Find all IPA servers:
- ipa server-find
-""") + _("""
- Find all suffixes:
- ipa topologysuffix-find
-""") + _("""
- Add topology segment to 'domain' suffix:
- ipa topologysegment-add domain --left IPA_SERVER_A --right IPA_SERVER_B
-""") + _("""
- Add topology segment to 'ca' suffix:
- ipa topologysegment-add ca --left IPA_SERVER_A --right IPA_SERVER_B
-""") + _("""
- List all topology segments in 'domain' suffix:
- ipa topologysegment-find domain
-""") + _("""
- List all topology segments in 'ca' suffix:
- ipa topologysegment-find ca
-""") + _("""
- Delete topology segment in 'domain' suffix:
- ipa topologysegment-del domain segment_name
-""") + _("""
- Delete topology segment in 'ca' suffix:
- ipa topologysegment-del ca segment_name
-""") + _("""
- Verify topology of 'domain' suffix:
- ipa topologysuffix-verify domain
-""") + _("""
- Verify topology of 'ca' suffix:
- ipa topologysuffix-verify ca
-""")
-
-register = Registry()
-
-
-def validate_domain_level(api):
- current = int(api.Command.domainlevel_get()['result'])
- if current < DOMAIN_LEVEL_1:
- raise errors.InvalidDomainLevelError(
- reason=_('Topology management requires minimum domain level {0} '
- .format(DOMAIN_LEVEL_1))
- )
-
-
-@register()
-class topologysegment(LDAPObject):
- """
- Topology segment.
- """
- parent_object = 'topologysuffix'
- container_dn = api.env.container_topology
- object_name = _('segment')
- object_name_plural = _('segments')
- object_class = ['iparepltoposegment']
- default_attributes = [
- 'cn',
- 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode',
- 'ipaReplTopoSegmentLeftNode', 'nsds5replicastripattrs',
- 'nsds5replicatedattributelist', 'nsds5replicatedattributelisttotal',
- 'nsds5replicatimeout', 'nsds5replicaenabled'
- ]
- search_display_attributes = [
- 'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode',
- 'ipaReplTopoSegmentLeftNode'
- ]
-
- label = _('Topology Segments')
- label_singular = _('Topology Segment')
-
- takes_params = (
- Str(
- 'cn',
- maxlength=255,
- cli_name='name',
- primary_key=True,
- label=_('Segment name'),
- default_from=lambda iparepltoposegmentleftnode, iparepltoposegmentrightnode:
- '%s-to-%s' % (iparepltoposegmentleftnode, iparepltoposegmentrightnode),
- normalizer=lambda value: value.lower(),
- doc=_('Arbitrary string identifying the segment'),
- ),
- Str(
- 'iparepltoposegmentleftnode',
- pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$',
- pattern_errmsg='may only include letters, numbers, -, . and $',
- maxlength=255,
- cli_name='leftnode',
- label=_('Left node'),
- normalizer=lambda value: value.lower(),
- doc=_('Left replication node - an IPA server'),
- flags={'no_update'},
- ),
- Str(
- 'iparepltoposegmentrightnode',
- pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]{0,252}[a-zA-Z0-9.$-]?$',
- pattern_errmsg='may only include letters, numbers, -, . and $',
- maxlength=255,
- cli_name='rightnode',
- label=_('Right node'),
- normalizer=lambda value: value.lower(),
- doc=_('Right replication node - an IPA server'),
- flags={'no_update'},
- ),
- StrEnum(
- 'iparepltoposegmentdirection',
- cli_name='direction',
- label=_('Connectivity'),
- values=(u'both', u'left-right', u'right-left'),
- default=u'both',
- autofill=True,
- doc=_('Direction of replication between left and right replication '
- 'node'),
- flags={'no_option', 'no_update'},
- ),
- Str(
- 'nsds5replicastripattrs?',
- cli_name='stripattrs',
- label=_('Attributes to strip'),
- normalizer=lambda value: value.lower(),
- doc=_('A space separated list of attributes which are removed from '
- 'replication updates.')
- ),
- Str(
- 'nsds5replicatedattributelist?',
- cli_name='replattrs',
- label='Attributes to replicate',
- doc=_('Attributes that are not replicated to a consumer server '
- 'during a fractional update. E.g., `(objectclass=*) '
- '$ EXCLUDE accountlockout memberof'),
- ),
- Str(
- 'nsds5replicatedattributelisttotal?',
- cli_name='replattrstotal',
- label=_('Attributes for total update'),
- doc=_('Attributes that are not replicated to a consumer server '
- 'during a total update. E.g. (objectclass=*) $ EXCLUDE '
- 'accountlockout'),
- ),
- Int(
- 'nsds5replicatimeout?',
- cli_name='timeout',
- label=_('Session timeout'),
- minvalue=0,
- doc=_('Number of seconds outbound LDAP operations waits for a '
- 'response from the remote replica before timing out and '
- 'failing'),
- ),
- StrEnum(
- 'nsds5replicaenabled?',
- cli_name='enabled',
- label=_('Replication agreement enabled'),
- doc=_('Whether a replication agreement is active, meaning whether '
- 'replication is occurring per that agreement'),
- values=(u'on', u'off'),
- flags={'no_option'},
- ),
- )
-
- def validate_nodes(self, ldap, dn, entry_attrs):
- leftnode = entry_attrs.get('iparepltoposegmentleftnode')
- rightnode = entry_attrs.get('iparepltoposegmentrightnode')
-
- if not leftnode and not rightnode:
- return # nothing to check
-
- # check if nodes are IPA servers
- masters = self.api.Command.server_find(
- '', sizelimit=0, no_members=False)['result']
- m_hostnames = [master['cn'][0].lower() for master in masters]
-
- if leftnode and leftnode not in m_hostnames:
- raise errors.ValidationError(
- name='leftnode',
- error=_('left node is not a topology node: %(leftnode)s') %
- dict(leftnode=leftnode)
- )
-
- if rightnode and rightnode not in m_hostnames:
- raise errors.ValidationError(
- name='rightnode',
- error=_('right node is not a topology node: %(rightnode)s') %
- dict(rightnode=rightnode)
- )
-
- # prevent creation of reflexive relation
- key = 'leftnode'
- if not leftnode or not rightnode: # get missing end
- _entry_attrs = ldap.get_entry(dn, ['*'])
- if not leftnode:
- key = 'rightnode'
- leftnode = _entry_attrs['iparepltoposegmentleftnode'][0]
- else:
- rightnode = _entry_attrs['iparepltoposegmentrightnode'][0]
-
- if leftnode == rightnode:
- raise errors.ValidationError(
- name=key,
- error=_('left node and right node must not be the same')
- )
-
-
-@register()
-class topologysegment_find(LDAPSearch):
- __doc__ = _('Search for topology segments.')
-
- msg_summary = ngettext(
- '%(count)d segment matched',
- '%(count)d segments matched', 0
- )
-
-
-@register()
-class topologysegment_add(LDAPCreate):
- __doc__ = _('Add a new segment.')
-
- msg_summary = _('Added segment "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- self.obj.validate_nodes(ldap, dn, entry_attrs)
- return dn
-
-
-@register()
-class topologysegment_del(LDAPDelete):
- __doc__ = _('Delete a segment.')
-
- msg_summary = _('Deleted segment "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- return dn
-
-
-@register()
-class topologysegment_mod(LDAPUpdate):
- __doc__ = _('Modify a segment.')
-
- msg_summary = _('Modified segment "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- self.obj.validate_nodes(ldap, dn, entry_attrs)
- return dn
-
-
-@register()
-class topologysegment_reinitialize(LDAPQuery):
- __doc__ = _('Request a full re-initialization of the node '
- 'retrieving data from the other node.')
-
- has_output = output.standard_value
- msg_summary = _('%(value)s')
-
- takes_options = (
- Flag(
- 'left?',
- doc=_('Initialize left node'),
- default=False,
- ),
- Flag(
- 'right?',
- doc=_('Initialize right node'),
- default=False,
- ),
- Flag(
- 'stop?',
- doc=_('Stop already started refresh of chosen node(s)'),
- default=False,
- ),
- )
-
- def execute(self, *keys, **options):
- dn = self.obj.get_dn(*keys, **options)
- validate_domain_level(self.api)
-
- entry = self.obj.backend.get_entry(
- dn, [
- 'nsds5beginreplicarefresh;left',
- 'nsds5beginreplicarefresh;right'
- ])
-
- left = options.get('left')
- right = options.get('right')
- stop = options.get('stop')
-
- if not left and not right:
- raise errors.OptionError(
- _('left or right node has to be specified')
- )
-
- if left and right:
- raise errors.OptionError(
- _('only one node can be specified')
- )
-
- action = u'start'
- msg = _('Replication refresh for segment: "%(pkey)s" requested.')
- if stop:
- action = u'stop'
- msg = _('Stopping of replication refresh for segment: "'
- '%(pkey)s" requested.')
-
- # left and right are swapped because internally it's a push not
- # pull operation
- if right:
- entry['nsds5beginreplicarefresh;left'] = [action]
- if left:
- entry['nsds5beginreplicarefresh;right'] = [action]
-
- self.obj.backend.update_entry(entry)
-
- msg = msg % {'pkey': keys[-1]}
- return dict(
- result=True,
- value=msg,
- )
-
-
-@register()
-class topologysegment_show(LDAPRetrieve):
- __doc__ = _('Display a segment.')
-
-
-@register()
-class topologysuffix(LDAPObject):
- """
- Suffix managed by the topology plugin.
- """
- container_dn = api.env.container_topology
- object_name = _('suffix')
- object_name_plural = _('suffixes')
- object_class = ['iparepltopoconf']
- default_attributes = ['cn', 'ipaReplTopoConfRoot']
- search_display_attributes = ['cn', 'ipaReplTopoConfRoot']
- label = _('Topology suffixes')
- label_singular = _('Topology suffix')
-
- takes_params = (
- Str(
- 'cn',
- cli_name='name',
- primary_key=True,
- label=_('Suffix name'),
- ),
- DNParam(
- 'iparepltopoconfroot',
- cli_name='suffix_dn',
- label=_('Managed LDAP suffix DN'),
- ),
- )
-
-
-@register()
-class topologysuffix_find(LDAPSearch):
- __doc__ = _('Search for topology suffixes.')
-
- msg_summary = ngettext(
- '%(count)d topology suffix matched',
- '%(count)d topology suffixes matched', 0
- )
-
-
-@register()
-class topologysuffix_del(LDAPDelete):
- __doc__ = _('Delete a topology suffix.')
-
- NO_CLI = True
-
- msg_summary = _('Deleted topology suffix "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- return dn
-
-
-@register()
-class topologysuffix_add(LDAPCreate):
- __doc__ = _('Add a new topology suffix to be managed.')
-
- NO_CLI = True
-
- msg_summary = _('Added topology suffix "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- return dn
-
-
-@register()
-class topologysuffix_mod(LDAPUpdate):
- __doc__ = _('Modify a topology suffix.')
-
- NO_CLI = True
-
- msg_summary = _('Modified topology suffix "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
- validate_domain_level(self.api)
- return dn
-
-
-@register()
-class topologysuffix_show(LDAPRetrieve):
- __doc__ = _('Show managed suffix.')
-
-
-@register()
-class topologysuffix_verify(LDAPQuery):
- __doc__ = _('''
-Verify replication topology for suffix.
-
-Checks done:
- 1. check if a topology is not disconnected. In other words if there are
- replication paths between all servers.
- 2. check if servers don't have more than the recommended number of
- replication agreements
-''')
-
- def execute(self, *keys, **options):
-
- validate_domain_level(self.api)
-
- masters = self.api.Command.server_find(
- '', sizelimit=0, no_members=False)['result']
- segments = self.api.Command.topologysegment_find(
- keys[0], sizelimit=0)['result']
- graph = create_topology_graph(masters, segments)
- master_cns = [m['cn'][0] for m in masters]
- master_cns.sort()
-
- # check if each master can contact others
- connect_errors = get_topology_connection_errors(graph)
-
- # check if suggested maximum number of agreements per replica
- max_agmts_errors = []
- for m in master_cns:
- # chosen direction doesn't matter much given that 'both' is the
- # only allowed direction
- suppliers = graph.get_tails(m)
- if len(suppliers) > self.api.env.recommended_max_agmts:
- max_agmts_errors.append((m, suppliers))
-
- return dict(
- result={
- 'in_order': not connect_errors and not max_agmts_errors,
- 'connect_errors': connect_errors,
- 'max_agmts_errors': max_agmts_errors,
- 'max_agmts': self.api.env.recommended_max_agmts
- },
- )
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
deleted file mode 100644
index ee0ab5d10..000000000
--- a/ipalib/plugins/trust.py
+++ /dev/null
@@ -1,1725 +0,0 @@
-# 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/>.
-
-import six
-
-from ipalib.messages import (
- add_message,
- BrokenTrust)
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- entry_to_dict,
- LDAPCreate,
- LDAPDelete,
- LDAPUpdate,
- LDAPSearch,
- LDAPRetrieve,
- LDAPObject,
- LDAPQuery)
-from .dns import dns_container_exists
-from ipapython.dn import DN
-from ipapython.ipautil import realm_to_suffix
-from ipapython.ipa_log_manager import root_logger
-from ipalib import api, Str, StrEnum, Password, Bool, _, ngettext, Int, Flag
-from ipalib import Command
-from ipalib import errors
-from ipalib import output
-from ldap import SCOPE_SUBTREE
-from time import sleep
-
-if six.PY3:
- unicode = str
-
-try:
- import pysss_murmur #pylint: disable=F0401
- _murmur_installed = True
-except Exception as e:
- _murmur_installed = False
-
-try:
- import pysss_nss_idmap #pylint: disable=F0401
- _nss_idmap_installed = True
-except Exception as e:
- _nss_idmap_installed = False
-
-if api.env.in_server and api.env.context in ['lite', 'server']:
- try:
- import ipaserver.dcerpc #pylint: disable=F0401
- from ipaserver.dcerpc import TRUST_ONEWAY, TRUST_BIDIRECTIONAL
- import dbus
- import dbus.mainloop.glib
- _bindings_installed = True
- except ImportError:
- _bindings_installed = False
-
-__doc__ = _("""
-Cross-realm trusts
-
-Manage trust relationship between IPA and Active Directory domains.
-
-In order to allow users from a remote domain to access resources in IPA
-domain, trust relationship needs to be established. Currently IPA supports
-only trusts between IPA and Active Directory domains under control of Windows
-Server 2008 or later, with functional level 2008 or later.
-
-Please note that DNS on both IPA and Active Directory domain sides should be
-configured properly to discover each other. Trust relationship relies on
-ability to discover special resources in the other domain via DNS records.
-
-Examples:
-
-1. Establish cross-realm trust with Active Directory using AD administrator
- credentials:
-
- ipa trust-add --type=ad <ad.domain> --admin <AD domain administrator> --password
-
-2. List all existing trust relationships:
-
- ipa trust-find
-
-3. Show details of the specific trust relationship:
-
- ipa trust-show <ad.domain>
-
-4. Delete existing trust relationship:
-
- ipa trust-del <ad.domain>
-
-Once trust relationship is established, remote users will need to be mapped
-to local POSIX groups in order to actually use IPA resources. The mapping should
-be done via use of external membership of non-POSIX group and then this group
-should be included into one of local POSIX groups.
-
-Example:
-
-1. Create group for the trusted domain admins' mapping and their local POSIX group:
-
- ipa group-add --desc='<ad.domain> admins external map' ad_admins_external --external
- ipa group-add --desc='<ad.domain> admins' ad_admins
-
-2. Add security identifier of Domain Admins of the <ad.domain> to the ad_admins_external
- group:
-
- ipa group-add-member ad_admins_external --external 'AD\\Domain Admins'
-
-3. Allow members of ad_admins_external group to be associated with ad_admins POSIX group:
-
- ipa group-add-member ad_admins --groups ad_admins_external
-
-4. List members of external members of ad_admins_external group to see their SIDs:
-
- ipa group-show ad_admins_external
-
-
-GLOBAL TRUST CONFIGURATION
-
-When IPA AD trust subpackage is installed and ipa-adtrust-install is run,
-a local domain configuration (SID, GUID, NetBIOS name) is generated. These
-identifiers are then used when communicating with a trusted domain of the
-particular type.
-
-1. Show global trust configuration for Active Directory type of trusts:
-
- ipa trustconfig-show --type ad
-
-2. Modify global configuration for all trusts of Active Directory type and set
- a different fallback primary group (fallback primary group GID is used as
- a primary user GID if user authenticating to IPA domain does not have any other
- primary GID already set):
-
- ipa trustconfig-mod --type ad --fallback-primary-group "alternative AD group"
-
-3. Change primary fallback group back to default hidden group (any group with
- posixGroup object class is allowed):
-
- ipa trustconfig-mod --type ad --fallback-primary-group "Default SMB Group"
-""")
-
-register = Registry()
-
-trust_output_params = (
- Str('trustdirection',
- label=_('Trust direction')),
- Str('trusttype',
- label=_('Trust type')),
- Str('truststatus',
- label=_('Trust status')),
-)
-
-_trust_type_dict = {1 : _('Non-Active Directory domain'),
- 2 : _('Active Directory domain'),
- 3 : _('RFC4120-compliant Kerberos realm')}
-_trust_direction_dict = {1 : _('Trusting forest'),
- 2 : _('Trusted forest'),
- 3 : _('Two-way trust')}
-_trust_status_dict = {True : _('Established and verified'),
- False : _('Waiting for confirmation by remote side')}
-_trust_type_dict_unknown = _('Unknown')
-
-_trust_type_option = StrEnum('trust_type',
- cli_name='type',
- label=_('Trust type (ad for Active Directory, default)'),
- values=(u'ad',),
- default=u'ad',
- autofill=True,
- )
-
-DEFAULT_RANGE_SIZE = 200000
-
-DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
-
-CRED_STYLE_SAMBA = 1
-CRED_STYLE_KERBEROS = 2
-
-def trust_type_string(level):
- """
- Returns a string representing a type of the trust. The original field is an enum:
- LSA_TRUST_TYPE_DOWNLEVEL = 0x00000001,
- LSA_TRUST_TYPE_UPLEVEL = 0x00000002,
- LSA_TRUST_TYPE_MIT = 0x00000003
- """
- string = _trust_type_dict.get(int(level), _trust_type_dict_unknown)
- return unicode(string)
-
-def trust_direction_string(level):
- """
- Returns a string representing a direction of the trust. The original field is a bitmask taking two bits in use
- LSA_TRUST_DIRECTION_INBOUND = 0x00000001,
- LSA_TRUST_DIRECTION_OUTBOUND = 0x00000002
- """
- string = _trust_direction_dict.get(int(level), _trust_type_dict_unknown)
- return unicode(string)
-
-def trust_status_string(level):
- string = _trust_status_dict.get(level, _trust_type_dict_unknown)
- return unicode(string)
-
-def make_trust_dn(env, trust_type, dn):
- assert isinstance(dn, DN)
- if trust_type:
- container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn)
- return DN(dn, container_dn)
- return dn
-
-def find_adtrust_masters(ldap, api):
- """
- Returns a list of names of IPA servers with ADTRUST component configured.
- """
-
- try:
- entries, truncated = ldap.find_entries(
- "cn=ADTRUST",
- base_dn=api.env.container_masters + api.env.basedn
- )
- except errors.NotFound:
- entries = []
-
- return [entry.dn[1].value for entry in entries]
-
-def verify_samba_component_presence(ldap, api):
- """
- Verifies that Samba is installed and configured on this particular master.
- If Samba is not available, provide a heplful hint with the list of masters
- capable of running the commands.
- """
-
- adtrust_present = api.Command['adtrust_is_enabled']()['result']
-
- hint = _(
- ' Alternatively, following servers are capable of running this '
- 'command: %(masters)s'
- )
-
- def raise_missing_component_error(message):
- masters_with_adtrust = find_adtrust_masters(ldap, api)
-
- # If there are any masters capable of running Samba requiring commands
- # let's advertise them directly
- if masters_with_adtrust:
- message += hint % dict(masters=', '.join(masters_with_adtrust))
-
- raise errors.NotFound(
- name=_('AD Trust setup'),
- reason=message,
- )
-
- # We're ok in this case, bail out
- if adtrust_present and _bindings_installed:
- return
-
- # First check for packages missing
- elif not _bindings_installed:
- error_message=_(
- 'Cannot perform the selected command without Samba 4 support '
- 'installed. Make sure you have installed server-trust-ad '
- 'sub-package of IPA.'
- )
-
- raise_missing_component_error(error_message)
-
- # Packages present, but ADTRUST instance is not configured
- elif not adtrust_present:
- error_message=_(
- 'Cannot perform the selected command without Samba 4 instance '
- 'configured on this machine. Make sure you have run '
- 'ipa-adtrust-install on this server.'
- )
-
- raise_missing_component_error(error_message)
-
-
-def generate_creds(trustinstance, style, **options):
- """
- Generate string representing credentials using trust instance
- Input:
- trustinstance -- ipaserver.dcerpc.TrustInstance object
- style -- style of credentials
- CRED_STYLE_SAMBA -- for using with Samba bindings
- CRED_STYLE_KERBEROS -- for obtaining Kerberos ticket
- **options -- options with realm_admin and realm_passwd keys
-
- Result:
- a string representing credentials with first % separating username and password
- None is returned if realm_passwd key returns nothing from options
- """
- creds = None
- password = options.get('realm_passwd', None)
- if password:
- admin_name = options.get('realm_admin')
- sp = []
- sep = '@'
- if style == CRED_STYLE_SAMBA:
- sep = "\\"
- sp = admin_name.split(sep)
- if len(sp) == 1:
- sp.insert(0, trustinstance.remote_domain.info['name'])
- elif style == CRED_STYLE_KERBEROS:
- sp = admin_name.split('\\')
- if len(sp) > 1:
- sp = [sp[1]]
- else:
- sp = admin_name.split(sep)
- if len(sp) == 1:
- sp.append(trustinstance.remote_domain.info['dns_forest'].upper())
- creds = u"{name}%{password}".format(name=sep.join(sp),
- password=password)
- return creds
-
-def add_range(myapi, trustinstance, range_name, dom_sid, *keys, **options):
- """
- First, we try to derive the parameters of the ID range based on the
- information contained in the Active Directory.
-
- If that was not successful, we go for our usual defaults (random base,
- range size 200 000, ipa-ad-trust range type).
-
- Any of these can be overridden by passing appropriate CLI options
- to the trust-add command.
- """
-
- range_size = None
- range_type = None
- base_id = None
-
- # First, get information about ID space from AD
- # However, we skip this step if other than ipa-ad-trust-posix
- # range type is enforced
-
- if options.get('range_type', None) in (None, u'ipa-ad-trust-posix'):
-
- # Get the base dn
- domain = keys[-1]
- basedn = realm_to_suffix(domain)
-
- # Search for information contained in
- # CN=ypservers,CN=ypServ30,CN=RpcServices,CN=System
- info_filter = '(objectClass=msSFU30DomainInfo)'
- info_dn = DN('CN=ypservers,CN=ypServ30,CN=RpcServices,CN=System')\
- + basedn
-
- # Get the domain validator
- domain_validator = ipaserver.dcerpc.DomainValidator(myapi)
- if not domain_validator.is_configured():
- raise errors.NotFound(
- reason=_('Cannot search in trusted domains without own '
- 'domain configured. Make sure you have run '
- 'ipa-adtrust-install on the IPA server first'))
-
- creds = None
- if trustinstance:
- # Re-use AD administrator credentials if they were provided
- creds = generate_creds(trustinstance, style=CRED_STYLE_KERBEROS, **options)
- if creds:
- domain_validator._admin_creds = creds
- # KDC might not get refreshed data at the first time,
- # retry several times
- for retry in range(10):
- info_list = domain_validator.search_in_dc(domain,
- info_filter,
- None,
- SCOPE_SUBTREE,
- basedn=info_dn,
- quiet=True)
-
- if info_list:
- info = info_list[0]
- break
- else:
- sleep(2)
-
- required_msSFU_attrs = ['msSFU30MaxUidNumber', 'msSFU30OrderNumber']
-
- if not info_list:
- # We were unable to gain UNIX specific info from the AD
- root_logger.debug("Unable to gain POSIX info from the AD")
- else:
- if all(attr in info for attr in required_msSFU_attrs):
- root_logger.debug("Able to gain POSIX info from the AD")
- range_type = u'ipa-ad-trust-posix'
-
- max_uid = info.get('msSFU30MaxUidNumber')
- max_gid = info.get('msSFU30MaxGidNumber', None)
- max_id = int(max(max_uid, max_gid)[0])
-
- base_id = int(info.get('msSFU30OrderNumber')[0])
- range_size = (1 + (max_id - base_id) // DEFAULT_RANGE_SIZE)\
- * DEFAULT_RANGE_SIZE
-
- # Second, options given via the CLI options take precedence to discovery
- if options.get('range_type', None):
- range_type = options.get('range_type', None)
- elif not range_type:
- range_type = u'ipa-ad-trust'
-
- if options.get('range_size', None):
- range_size = options.get('range_size', None)
- elif not range_size:
- range_size = DEFAULT_RANGE_SIZE
-
- if options.get('base_id', None):
- base_id = options.get('base_id', None)
- elif not base_id:
- # Generate random base_id if not discovered nor given via CLI
- base_id = DEFAULT_RANGE_SIZE + (
- pysss_murmur.murmurhash3(
- dom_sid,
- len(dom_sid), 0xdeadbeef
- ) % 10000
- ) * DEFAULT_RANGE_SIZE
-
- # Finally, add new ID range
- myapi.Command['idrange_add'](range_name,
- ipabaseid=base_id,
- ipaidrangesize=range_size,
- ipabaserid=0,
- iparangetype=range_type,
- ipanttrusteddomainsid=dom_sid)
-
- # Return the values that were generated inside this function
- return range_type, range_size, base_id
-
-def fetch_trusted_domains_over_dbus(myapi, log, forest_name):
- if not _bindings_installed:
- return
- # Calling oddjobd-activated service via DBus has some quirks:
- # - Oddjobd registers multiple canonical names on the same address
- # - python-dbus only follows name owner changes when mainloop is in use
- # See https://fedorahosted.org/oddjob/ticket/2 for details
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- try:
- _ret = 0
- _stdout = ''
- _stderr = ''
- bus = dbus.SystemBus()
- intf = bus.get_object(DBUS_IFACE_TRUST,"/", follow_name_owner_changes=True)
- fetch_domains_method = intf.get_dbus_method('fetch_domains', dbus_interface=DBUS_IFACE_TRUST)
- (_ret, _stdout, _stderr) = fetch_domains_method(forest_name)
- except dbus.DBusException as e:
- log.error('Failed to call %(iface)s.fetch_domains helper.'
- 'DBus exception is %(exc)s.' % dict(iface=DBUS_IFACE_TRUST, exc=str(e)))
- if _ret != 0:
- log.error('Helper was called for forest %(forest)s, return code is %(ret)d' % dict(forest=forest_name, ret=_ret))
- log.error('Standard output from the helper:\n%s---\n' % (_stdout))
- log.error('Error output from the helper:\n%s--\n' % (_stderr))
- raise errors.ServerCommandError(server=myapi.env.host,
- error=_('Fetching domains from trusted forest failed. '
- 'See details in the error_log'))
- return
-
-@register()
-class trust(LDAPObject):
- """
- Trust object.
- """
- trust_types = ('ad', 'ipa')
- container_dn = api.env.container_trusts
- object_name = _('trust')
- object_name_plural = _('trusts')
- object_class = ['ipaNTTrustedDomain']
- default_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid',
- 'ipanttrusttype', 'ipanttrustattributes', 'ipanttrustdirection',
- 'ipanttrustpartner', 'ipanttrustforesttrustinfo',
- 'ipanttrustposixoffset', 'ipantsupportedencryptiontypes' ]
- search_display_attributes = ['cn', 'ipantflatname',
- 'ipanttrusteddomainsid', 'ipanttrusttype']
- managed_permissions = {
- 'System: Read Trust Information': {
- # Allow reading of attributes needed for SSSD subdomains support
- 'non_object': True,
- 'ipapermlocation': DN(container_dn, api.env.basedn),
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'cn', 'objectclass',
- 'ipantflatname', 'ipantsecurityidentifier',
- 'ipanttrusteddomainsid', 'ipanttrustpartner',
- 'ipantsidblacklistincoming', 'ipantsidblacklistoutgoing',
- 'ipanttrustdirection'
- },
- },
-
- 'System: Read system trust accounts': {
- 'non_object': True,
- 'ipapermlocation': DN(container_dn, api.env.basedn),
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'uidnumber', 'gidnumber', 'krbprincipalname'
- },
- 'default_privileges': {'ADTrust Agents'},
- },
- }
-
- label = _('Trusts')
- label_singular = _('Trust')
-
- takes_params = (
- Str('cn',
- cli_name='realm',
- label=_('Realm name'),
- primary_key=True,
- ),
- Str('ipantflatname',
- cli_name='flat_name',
- label=_('Domain NetBIOS name'),
- flags=['no_create', 'no_update']),
- Str('ipanttrusteddomainsid',
- cli_name='sid',
- label=_('Domain Security Identifier'),
- flags=['no_create', 'no_update']),
- Str('ipantsidblacklistincoming*',
- cli_name='sid_blacklist_incoming',
- label=_('SID blacklist incoming'),
- flags=['no_create']),
- Str('ipantsidblacklistoutgoing*',
- cli_name='sid_blacklist_outgoing',
- label=_('SID blacklist outgoing'),
- flags=['no_create']),
- )
-
- def validate_sid_blacklists(self, entry_attrs):
- if not _bindings_installed:
- # SID validator is not available, return
- # Even if invalid SID gets in the trust entry, it won't crash
- # the validation process as it is translated to SID S-0-0
- return
- for attr in ('ipantsidblacklistincoming', 'ipantsidblacklistoutgoing'):
- values = entry_attrs.get(attr)
- if not values:
- continue
- for value in values:
- if not ipaserver.dcerpc.is_sid_valid(value):
- raise errors.ValidationError(name=attr,
- error=_("invalid SID: %(value)s") % dict(value=value))
-
- def get_dn(self, *keys, **kwargs):
- trust_type = kwargs.get('trust_type')
-
- sdn = [('cn', x) for x in keys]
- sdn.reverse()
-
- if trust_type is None:
- ldap = self.backend
- trustfilter = ldap.make_filter({
- 'objectclass': ['ipaNTTrustedDomain'],
- 'cn': [keys[-1]]},
- rules=ldap.MATCH_ALL
- )
-
- # more type of objects can be located in subtree (for example
- # cross-realm principals). we need this attr do detect trust
- # entries
- trustfilter = ldap.combine_filters(
- (trustfilter, "ipaNTTrustPartner=*"),
- rules=ldap.MATCH_ALL
- )
-
- try:
- result = ldap.get_entries(
- DN(self.container_dn, self.env.basedn),
- ldap.SCOPE_SUBTREE, trustfilter, ['']
- )
- except errors.NotFound:
- self.handle_not_found(keys[-1])
-
- if len(result) > 1:
- raise errors.OnlyOneValueAllowed(attr='trust domain')
-
- return result[0].dn
-
- return make_trust_dn(self.env, trust_type, DN(*sdn))
-
- def warning_if_ad_trust_dom_have_missing_SID(self, result, **options):
- """Due bug https://fedorahosted.org/freeipa/ticket/5665 there might be
- AD trust domain without generated SID, warn user about it.
- """
- ldap = self.api.Backend.ldap2
-
- try:
- entries, truncated = ldap.find_entries(
- base_dn=DN(self.api.env.container_adtrusts,
- self.api.env.basedn),
- scope=ldap.SCOPE_ONELEVEL,
- attrs_list=['cn'],
- filter='(&(ipaNTTrustPartner=*)'
- '(!(ipaNTSecurityIdentifier=*)))',
- )
- except errors.NotFound:
- pass
- else:
- for entry in entries:
- add_message(
- options['version'],
- result,
- BrokenTrust(domain=entry.single_value['cn'])
- )
-
-
-@register()
-class trust_add(LDAPCreate):
- __doc__ = _('''
-Add new trust to use.
-
-This command establishes trust relationship to another domain
-which becomes 'trusted'. As result, users of the trusted domain
-may access resources of this domain.
-
-Only trusts to Active Directory domains are supported right now.
-
-The command can be safely run multiple times against the same domain,
-this will cause change to trust relationship credentials on both
-sides.
- ''')
-
- range_types = {
- u'ipa-ad-trust': unicode(_('Active Directory domain range')),
- u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
- 'POSIX attributes')),
- }
-
- takes_options = LDAPCreate.takes_options + (
- _trust_type_option,
- Str('realm_admin?',
- cli_name='admin',
- label=_("Active Directory domain administrator"),
- ),
- Password('realm_passwd?',
- cli_name='password',
- label=_("Active Directory domain administrator's password"),
- confirm=False,
- ),
- Str('realm_server?',
- cli_name='server',
- label=_('Domain controller for the Active Directory domain (optional)'),
- ),
- Password('trust_secret?',
- cli_name='trust_secret',
- label=_('Shared secret for the trust'),
- confirm=False,
- ),
- Int('base_id?',
- cli_name='base_id',
- label=_('First Posix ID of the range reserved for the trusted domain'),
- ),
- Int('range_size?',
- cli_name='range_size',
- label=_('Size of the ID range reserved for the trusted domain'),
- ),
- StrEnum('range_type?',
- label=_('Range type'),
- cli_name='range_type',
- doc=(_('Type of trusted domain ID range, one of {vals}'
- .format(vals=', '.join(range_types.keys())))),
- values=tuple(range_types.keys()),
- ),
- Bool('bidirectional?',
- label=_('Two-way trust'),
- cli_name='two_way',
- doc=(_('Establish bi-directional trust. By default trust is inbound one-way only.')),
- default=False,
- ),
- )
-
- msg_summary = _('Added Active Directory trust for realm "%(value)s"')
- msg_summary_existing = _('Re-established trust to domain "%(value)s"')
- has_output_params = LDAPCreate.has_output_params + trust_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- verify_samba_component_presence(ldap, self.api)
-
- full_join = self.validate_options(*keys, **options)
- old_range, range_name, dom_sid = self.validate_range(*keys, **options)
- result = self.execute_ad(full_join, *keys, **options)
-
- if not old_range:
- # Store the created range type, since for POSIX trusts no
- # ranges for the subdomains should be added, POSIX attributes
- # provide a global mapping across all subdomains
- (created_range_type, _, _) = add_range(self.api, self.trustinstance,
- range_name, dom_sid,
- *keys, **options)
- else:
- created_range_type = old_range['result']['iparangetype'][0]
-
- trust_filter = "cn=%s" % result['value']
- (trusts, truncated) = ldap.find_entries(
- base_dn=DN(self.api.env.container_trusts, self.api.env.basedn),
- filter=trust_filter)
-
- result['result'] = entry_to_dict(trusts[0], **options)
-
- # Fetch topology of the trust forest -- we need always to do it
- # for AD trusts, regardless of the type of idranges associated with it
- # Note that add_new_domains_from_trust will add needed ranges for
- # the algorithmic ID mapping case.
- if (options.get('trust_type') == u'ad' and
- options.get('trust_secret') is None):
- if options.get('bidirectional') == True:
- # Bidirectional trust allows us to use cross-realm TGT, so we can
- # run the call under original user's credentials
- res = fetch_domains_from_trust(self.api, self.trustinstance,
- result['result'], **options)
- domains = add_new_domains_from_trust(self.api, self.trustinstance,
- result['result'], res, **options)
- else:
- # One-way trust is more complex. We don't have cross-realm TGT
- # and cannot use IPA principals to authenticate against AD.
- # Instead, we have to use our trusted domain object's (TDO)
- # account in AD. Access to the credentials is limited and IPA
- # framework cannot access it directly. Instead, we call out to
- # oddjobd-activated higher privilege process that will use TDO
- # object credentials to authenticate to AD with Kerberos,
- # run DCE RPC calls to do discovery and will call
- # add_new_domains_from_trust() on its own.
- fetch_trusted_domains_over_dbus(self.api, self.log, result['value'])
-
- # Format the output into human-readable values
- result['result']['trusttype'] = [trust_type_string(
- result['result']['ipanttrusttype'][0])]
- result['result']['trustdirection'] = [trust_direction_string(
- result['result']['ipanttrustdirection'][0])]
- result['result']['truststatus'] = [trust_status_string(
- result['verified'])]
-
- del result['verified']
- result['result'].pop('ipanttrustauthoutgoing', None)
- result['result'].pop('ipanttrustauthincoming', None)
-
- return result
-
- def validate_options(self, *keys, **options):
- trusted_realm_domain = keys[-1]
-
- if not _murmur_installed and 'base_id' not in options:
- raise errors.ValidationError(
- name=_('missing base_id'),
- error=_(
- 'pysss_murmur is not available on the server '
- 'and no base-id is given.'
- )
- )
-
- if 'trust_type' not in options:
- raise errors.RequirementError(name='trust_type')
-
- if options['trust_type'] != u'ad':
- raise errors.ValidationError(
- name=_('trust type'),
- error=_('only "ad" is supported')
- )
-
- # Detect IPA-AD domain clash
- if self.api.env.domain.lower() == trusted_realm_domain.lower():
- raise errors.ValidationError(
- name=_('domain'),
- error=_('Cannot establish a trust to AD deployed in the same '
- 'domain as IPA. Such setup is not supported.')
- )
-
- # If domain name and realm does not match, IPA server is not be able
- # to establish trust with Active Directory.
-
- realm_not_matching_domain = (self.api.env.domain.upper() != self.api.env.realm)
-
- if options['trust_type'] == u'ad' and realm_not_matching_domain:
- raise errors.ValidationError(
- name=_('Realm-domain mismatch'),
- error=_('To establish trust with Active Directory, the '
- 'domain name and the realm name of the IPA server '
- 'must match')
- )
-
- self.trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
- if not self.trustinstance.configured:
- raise errors.NotFound(
- name=_('AD Trust setup'),
- reason=_(
- 'Cannot perform join operation without own domain '
- 'configured. Make sure you have run ipa-adtrust-install '
- 'on the IPA server first'
- )
- )
-
- # Obtain a list of IPA realm domains
- result = self.api.Command.realmdomains_show()['result']
- realm_domains = result['associateddomain']
-
- # Do not allow the AD's trusted realm domain in the list
- # of our realm domains
- if trusted_realm_domain.lower() in realm_domains:
- raise errors.ValidationError(
- name=_('AD Trust setup'),
- error=_(
- 'Trusted domain %(domain)s is included among '
- 'IPA realm domains. It needs to be removed '
- 'prior to establishing the trust. See the '
- '"ipa realmdomains-mod --del-domain" command.'
- ) % dict(domain=trusted_realm_domain)
- )
-
- self.realm_server = options.get('realm_server')
- self.realm_admin = options.get('realm_admin')
- self.realm_passwd = options.get('realm_passwd')
-
- if self.realm_admin:
- names = self.realm_admin.split('@')
-
- if len(names) > 1:
- # realm admin name is in UPN format, user@realm, check that
- # realm is the same as the one that we are attempting to trust
- if trusted_realm_domain.lower() != names[-1].lower():
- raise errors.ValidationError(
- name=_('AD Trust setup'),
- error=_(
- 'Trusted domain and administrator account use '
- 'different realms'
- )
- )
- self.realm_admin = names[0]
-
- if not self.realm_passwd:
- raise errors.ValidationError(
- name=_('AD Trust setup'),
- error=_('Realm administrator password should be specified')
- )
- return True
-
- return False
-
- def validate_range(self, *keys, **options):
- # If a range for this trusted domain already exists,
- # '--base-id' or '--range-size' options should not be specified
- range_name = keys[-1].upper() + '_id_range'
- range_type = options.get('range_type')
-
- try:
- old_range = self.api.Command['idrange_show'](range_name, raw=True)
- except errors.NotFound:
- old_range = None
-
- if options.get('trust_type') == u'ad':
- if range_type and range_type not in (u'ipa-ad-trust',
- u'ipa-ad-trust-posix'):
- raise errors.ValidationError(
- name=_('id range type'),
- error=_(
- 'Only the ipa-ad-trust and ipa-ad-trust-posix are '
- 'allowed values for --range-type when adding an AD '
- 'trust.'
- ))
-
- base_id = options.get('base_id')
- range_size = options.get('range_size')
-
- if old_range and (base_id or range_size):
- raise errors.ValidationError(
- name=_('id range'),
- error=_(
- 'An id range already exists for this trust. '
- 'You should either delete the old range, or '
- 'exclude --base-id/--range-size options from the command.'
- )
- )
-
- # If a range for this trusted domain already exists,
- # domain SID must also match
- self.trustinstance.populate_remote_domain(
- keys[-1],
- self.realm_server,
- self.realm_admin,
- self.realm_passwd
- )
- dom_sid = self.trustinstance.remote_domain.info['sid']
-
- if old_range:
- old_dom_sid = old_range['result']['ipanttrusteddomainsid'][0]
- old_range_type = old_range['result']['iparangetype'][0]
-
- if old_dom_sid != dom_sid:
- raise errors.ValidationError(
- name=_('range exists'),
- error=_(
- 'ID range with the same name but different domain SID '
- 'already exists. The ID range for the new trusted '
- 'domain must be created manually.'
- )
- )
-
- if range_type and range_type != old_range_type:
- raise errors.ValidationError(name=_('range type change'),
- error=_('ID range for the trusted domain already exists, '
- 'but it has a different type. Please remove the '
- 'old range manually, or do not enforce type '
- 'via --range-type option.'))
-
- return old_range, range_name, dom_sid
-
- def execute_ad(self, full_join, *keys, **options):
- # Join domain using full credentials and with random trustdom
- # secret (will be generated by the join method)
-
- # First see if the trust is already in place
- # Force retrieval of the trust object by not passing trust_type
- try:
- dn = self.obj.get_dn(keys[-1])
- except errors.NotFound:
- dn = None
-
- trust_type = TRUST_ONEWAY
- if options.get('bidirectional', False):
- trust_type = TRUST_BIDIRECTIONAL
- # 1. Full access to the remote domain. Use admin credentials and
- # generate random trustdom password to do work on both sides
- if full_join:
- try:
- result = self.trustinstance.join_ad_full_credentials(
- keys[-1],
- self.realm_server,
- self.realm_admin,
- self.realm_passwd,
- trust_type
- )
- except errors.NotFound:
- error_message=_("Unable to resolve domain controller for '%s' domain. ") % (keys[-1])
- instructions=[]
- if dns_container_exists(self.obj.backend):
- try:
- dns_zone = self.api.Command.dnszone_show(keys[-1])['result']
- if ('idnsforwardpolicy' in dns_zone) and dns_zone['idnsforwardpolicy'][0] == u'only':
- instructions.append(_("Forward policy is defined for it in IPA DNS, "
- "perhaps forwarder points to incorrect host?"))
- except (errors.NotFound, KeyError) as e:
- instructions.append(_("IPA manages DNS, please verify "
- "your DNS configuration and "
- "make sure that service records "
- "of the '%(domain)s' domain can "
- "be resolved. Examples how to "
- "configure DNS with CLI commands "
- "or the Web UI can be found in "
- "the documentation. " ) %
- dict(domain=keys[-1]))
- else:
- instructions.append(_("Since IPA does not manage DNS records, ensure DNS "
- "is configured to resolve '%(domain)s' domain from "
- "IPA hosts and back.") % dict(domain=keys[-1]))
- raise errors.NotFound(reason=error_message, instructions=instructions)
-
- if result is None:
- raise errors.ValidationError(name=_('AD Trust setup'),
- error=_('Unable to verify write permissions to the AD'))
-
- ret = dict(
- value=pkey_to_value(
- self.trustinstance.remote_domain.info['dns_domain'],
- options),
- verified=result['verified']
- )
- if dn:
- ret['summary'] = self.msg_summary_existing % ret
- return ret
-
- # 2. We don't have access to the remote domain and trustdom password
- # is provided. Do the work on our side and inform what to do on remote
- # side.
- if options.get('trust_secret'):
- result = self.trustinstance.join_ad_ipa_half(
- keys[-1],
- self.realm_server,
- options['trust_secret'],
- trust_type
- )
- ret = dict(
- value=pkey_to_value(
- self.trustinstance.remote_domain.info['dns_domain'],
- options),
- verified=result['verified']
- )
- if dn:
- ret['summary'] = self.msg_summary_existing % ret
- return ret
- else:
- raise errors.ValidationError(
- name=_('AD Trust setup'),
- error=_('Not enough arguments specified to perform trust '
- 'setup'))
-
-@register()
-class trust_del(LDAPDelete):
- __doc__ = _('Delete a trust.')
-
- msg_summary = _('Deleted trust "%(value)s"')
-
-@register()
-class trust_mod(LDAPUpdate):
- __doc__ = _("""
- Modify a trust (for future use).
-
- Currently only the default option to modify the LDAP attributes is
- available. More specific options will be added in coming releases.
- """)
-
- msg_summary = _('Modified trust "%(value)s" '
- '(change will be effective in 60 seconds)')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- self.obj.validate_sid_blacklists(entry_attrs)
-
- return dn
-
-@register()
-class trust_find(LDAPSearch):
- __doc__ = _('Search for trusts.')
- has_output_params = LDAPSearch.has_output_params + trust_output_params +\
- (Str('ipanttrusttype'),)
-
- msg_summary = ngettext(
- '%(count)d trust matched', '%(count)d trusts matched', 0
- )
-
- # Since all trusts types are stored within separate containers under 'cn=trusts',
- # search needs to be done on a sub-tree scope
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
- # list only trust, not trust domains
- trust_filter = '(ipaNTTrustPartner=*)'
- filter = ldap.combine_filters((filters, trust_filter), rules=ldap.MATCH_ALL)
- return (filter, base_dn, ldap.SCOPE_SUBTREE)
-
- def execute(self, *args, **options):
- result = super(trust_find, self).execute(*args, **options)
-
- self.obj.warning_if_ad_trust_dom_have_missing_SID(result, **options)
-
- return result
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
-
- for attrs in entries:
- # Translate ipanttrusttype to trusttype if --raw not used
- trust_type = attrs.get('ipanttrusttype', [None])[0]
- if not options.get('raw', False) and trust_type is not None:
- attrs['trusttype'] = trust_type_string(attrs['ipanttrusttype'][0])
- del attrs['ipanttrusttype']
-
- return truncated
-
-@register()
-class trust_show(LDAPRetrieve):
- __doc__ = _('Display information about a trust.')
- has_output_params = LDAPRetrieve.has_output_params + trust_output_params +\
- (Str('ipanttrusttype'), Str('ipanttrustdirection'))
-
- def execute(self, *keys, **options):
- result = super(trust_show, self).execute(*keys, **options)
-
- self.obj.warning_if_ad_trust_dom_have_missing_SID(result, **options)
-
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
-
- assert isinstance(dn, DN)
- # Translate ipanttrusttype to trusttype
- # and ipanttrustdirection to trustdirection
- # if --raw not used
-
- if not options.get('raw', False):
- trust_type = entry_attrs.get('ipanttrusttype', [None])[0]
- if trust_type is not None:
- entry_attrs['trusttype'] = trust_type_string(trust_type)
- del entry_attrs['ipanttrusttype']
-
- dir_str = entry_attrs.get('ipanttrustdirection', [None])[0]
- if dir_str is not None:
- entry_attrs['trustdirection'] = [trust_direction_string(dir_str)]
- del entry_attrs['ipanttrustdirection']
-
- return dn
-
-
-_trustconfig_dn = {
- u'ad': DN(('cn', api.env.domain), api.env.container_cifsdomains, api.env.basedn),
-}
-
-
-@register()
-class trustconfig(LDAPObject):
- """
- Trusts global configuration object
- """
- object_name = _('trust configuration')
- default_attributes = [
- 'cn', 'ipantsecurityidentifier', 'ipantflatname', 'ipantdomainguid',
- 'ipantfallbackprimarygroup',
- ]
-
- label = _('Global Trust Configuration')
- label_singular = _('Global Trust Configuration')
-
- takes_params = (
- Str('cn',
- label=_('Domain'),
- flags=['no_update'],
- ),
- Str('ipantsecurityidentifier',
- label=_('Security Identifier'),
- flags=['no_update'],
- ),
- Str('ipantflatname',
- label=_('NetBIOS name'),
- flags=['no_update'],
- ),
- Str('ipantdomainguid',
- label=_('Domain GUID'),
- flags=['no_update'],
- ),
- Str('ipantfallbackprimarygroup',
- cli_name='fallback_primary_group',
- label=_('Fallback primary group'),
- ),
- )
-
- def get_dn(self, *keys, **kwargs):
- trust_type = kwargs.get('trust_type')
- if trust_type is None:
- raise errors.RequirementError(name='trust_type')
- try:
- return _trustconfig_dn[kwargs['trust_type']]
- except KeyError:
- raise errors.ValidationError(name='trust_type',
- error=_("unsupported trust type"))
-
- def _normalize_groupdn(self, entry_attrs):
- """
- Checks that group with given name/DN exists and updates the entry_attrs
- """
- if 'ipantfallbackprimarygroup' not in entry_attrs:
- return
-
- group = entry_attrs['ipantfallbackprimarygroup']
- if isinstance(group, (list, tuple)):
- group = group[0]
-
- if group is None:
- return
-
- try:
- dn = DN(group)
- # group is in a form of a DN
- try:
- self.backend.get_entry(dn)
- except errors.NotFound:
- self.api.Object['group'].handle_not_found(group)
- # DN is valid, we can just return
- return
- except ValueError:
- # The search is performed for groups with "posixgroup" objectclass
- # and not "ipausergroup" so that it can also match groups like
- # "Default SMB Group" which does not have this objectclass.
- try:
- group_entry = self.backend.find_entry_by_attr(
- self.api.Object['group'].primary_key.name,
- group,
- ['posixgroup'],
- [''],
- DN(self.api.env.container_group, self.api.env.basedn))
- except errors.NotFound:
- self.api.Object['group'].handle_not_found(group)
- else:
- entry_attrs['ipantfallbackprimarygroup'] = [group_entry.dn]
-
- def _convert_groupdn(self, entry_attrs, options):
- """
- Convert an group dn into a name. As we use CN as user RDN, its value
- can be extracted from the DN without further LDAP queries.
- """
- if options.get('raw', False):
- return
-
- try:
- groupdn = entry_attrs['ipantfallbackprimarygroup'][0]
- except (IndexError, KeyError):
- groupdn = None
-
- if groupdn is None:
- return
- assert isinstance(groupdn, DN)
-
- entry_attrs['ipantfallbackprimarygroup'] = [groupdn[0][0].value]
-
-
-@register()
-class trustconfig_mod(LDAPUpdate):
- __doc__ = _('Modify global trust configuration.')
-
- takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
- msg_summary = _('Modified "%(value)s" trust configuration')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- self.obj._normalize_groupdn(entry_attrs)
- return dn
-
- def execute(self, *keys, **options):
- result = super(trustconfig_mod, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['trust_type'], options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj._convert_groupdn(entry_attrs, options)
- return dn
-
-
-
-@register()
-class trustconfig_show(LDAPRetrieve):
- __doc__ = _('Show global trust configuration.')
-
- takes_options = LDAPRetrieve.takes_options + (_trust_type_option,)
-
- def execute(self, *keys, **options):
- result = super(trustconfig_show, self).execute(*keys, **options)
- result['value'] = pkey_to_value(options['trust_type'], options)
- return result
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj._convert_groupdn(entry_attrs, options)
- return dn
-
-
-if _nss_idmap_installed:
- _idmap_type_dict = {
- pysss_nss_idmap.ID_USER : 'user',
- pysss_nss_idmap.ID_GROUP : 'group',
- pysss_nss_idmap.ID_BOTH : 'both',
- }
- def idmap_type_string(level):
- string = _idmap_type_dict.get(int(level), 'unknown')
- return unicode(string)
-
-@register()
-class trust_resolve(Command):
- NO_CLI = True
- __doc__ = _('Resolve security identifiers of users and groups in trusted domains')
-
- takes_options = (
- Str('sids+',
- label = _('Security Identifiers (SIDs)'),
- ),
- )
-
- has_output_params = (
- Str('name', label= _('Name')),
- Str('sid', label= _('SID')),
- )
-
- has_output = (
- output.ListOfEntries('result'),
- )
-
- def execute(self, *keys, **options):
- result = list()
- if not _nss_idmap_installed:
- return dict(result=result)
- try:
- sids = [str(x) for x in options['sids']]
- xlate = pysss_nss_idmap.getnamebysid(sids)
- for sid in xlate:
- entry = dict()
- entry['sid'] = [unicode(sid)]
- entry['name'] = [unicode(xlate[sid][pysss_nss_idmap.NAME_KEY])]
- entry['type'] = [idmap_type_string(xlate[sid][pysss_nss_idmap.TYPE_KEY])]
- result.append(entry)
- except ValueError as e:
- pass
-
- return dict(result=result)
-
-
-
-@register()
-class adtrust_is_enabled(Command):
- NO_CLI = True
-
- __doc__ = _('Determine whether ipa-adtrust-install has been run on this '
- 'system')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- adtrust_dn = DN(
- ('cn', 'ADTRUST'),
- ('cn', self.api.env.host),
- ('cn', 'masters'),
- ('cn', 'ipa'),
- ('cn', 'etc'),
- self.api.env.basedn
- )
-
- try:
- ldap.get_entry(adtrust_dn)
- except errors.NotFound:
- return dict(result=False)
-
- return dict(result=True)
-
-
-
-@register()
-class compat_is_enabled(Command):
- NO_CLI = True
-
- __doc__ = _('Determine whether Schema Compatibility plugin is configured '
- 'to serve trusted domain users and groups')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- users_dn = DN(
- ('cn', 'users'),
- ('cn', 'Schema Compatibility'),
- ('cn', 'plugins'),
- ('cn', 'config')
- )
- groups_dn = DN(
- ('cn', 'groups'),
- ('cn', 'Schema Compatibility'),
- ('cn', 'plugins'),
- ('cn', 'config')
- )
-
- try:
- users_entry = ldap.get_entry(users_dn)
- except errors.NotFound:
- return dict(result=False)
-
- attr = users_entry.get('schema-compat-lookup-nsswitch')
- if not attr or 'user' not in attr:
- return dict(result=False)
-
- try:
- groups_entry = ldap.get_entry(groups_dn)
- except errors.NotFound:
- return dict(result=False)
-
- attr = groups_entry.get('schema-compat-lookup-nsswitch')
- if not attr or 'group' not in attr:
- return dict(result=False)
-
- return dict(result=True)
-
-
-
-@register()
-class sidgen_was_run(Command):
- """
- This command tries to determine whether the sidgen task was run during
- ipa-adtrust-install. It does that by simply checking the "editors" group
- for the presence of the ipaNTSecurityIdentifier attribute - if the
- attribute is present, the sidgen task was run.
-
- Since this command relies on the existence of the "editors" group, it will
- fail loudly in case this group does not exist.
- """
- NO_CLI = True
-
- __doc__ = _('Determine whether ipa-adtrust-install has been run with '
- 'sidgen task')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- editors_dn = DN(
- ('cn', 'editors'),
- ('cn', 'groups'),
- ('cn', 'accounts'),
- api.env.basedn
- )
-
- try:
- editors_entry = ldap.get_entry(editors_dn)
- except errors.NotFound:
- raise errors.NotFound(
- name=_('sidgen_was_run'),
- reason=_(
- 'This command relies on the existence of the "editors" '
- 'group, but this group was not found.'
- )
- )
-
- attr = editors_entry.get('ipaNTSecurityIdentifier')
- if not attr:
- return dict(result=False)
-
- return dict(result=True)
-
-
-@register()
-class trustdomain(LDAPObject):
- """
- Object representing a domain of the AD trust.
- """
- parent_object = 'trust'
- trust_type_idx = {'2':u'ad'}
- object_name = _('trust domain')
- object_name_plural = _('trust domains')
- object_class = ['ipaNTTrustedDomain']
- default_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', 'ipanttrustpartner']
- search_display_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', ]
-
- label = _('Trusted domains')
- label_singular = _('Trusted domain')
-
- takes_params = (
- Str('cn',
- label=_('Domain name'),
- cli_name='domain',
- primary_key=True
- ),
- Str('ipantflatname?',
- cli_name='flat_name',
- label=_('Domain NetBIOS name'),
- ),
- Str('ipanttrusteddomainsid?',
- cli_name='sid',
- label=_('Domain Security Identifier'),
- ),
- Str('ipanttrustpartner?',
- label=_('Trusted domain partner'),
- flags=['no_display', 'no_option'],
- ),
- )
-
- # LDAPObject.get_dn() only passes all but last element of keys and no kwargs
- # to the parent object's get_dn() no matter what you pass to it. Make own get_dn()
- # as we really need all elements to construct proper dn.
- def get_dn(self, *keys, **kwargs):
- sdn = [('cn', x) for x in keys]
- sdn.reverse()
- trust_type = kwargs.get('trust_type')
- if not trust_type:
- trust_type=u'ad'
-
- dn=make_trust_dn(self.env, trust_type, DN(*sdn))
- return dn
-
-@register()
-class trustdomain_find(LDAPSearch):
- __doc__ = _('Search domains of the trust')
-
- has_output_params = LDAPSearch.has_output_params + (
- Flag('domain_enabled', label= _('Domain enabled')),
- )
- def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
- return (filters, base_dn, ldap.SCOPE_SUBTREE)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
- trust_dn = self.obj.get_dn(args[0], trust_type=u'ad')
- trust_entry = ldap.get_entry(trust_dn)
- for entry in entries:
- sid = entry['ipanttrusteddomainsid'][0]
-
- blacklist = trust_entry.get('ipantsidblacklistincoming')
- if blacklist is None:
- continue
-
- if sid in blacklist:
- entry['domain_enabled'] = [False]
- else:
- entry['domain_enabled'] = [True]
- return truncated
-
-
-
-@register()
-class trustdomain_mod(LDAPUpdate):
- __doc__ = _('Modify trustdomain of the trust')
-
- NO_CLI = True
- takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
-
-@register()
-class trustdomain_add(LDAPCreate):
- __doc__ = _('Allow access from the trusted domain')
- NO_CLI = True
-
- takes_options = LDAPCreate.takes_options + (_trust_type_option,)
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- if 'ipanttrustpartner' in options:
- entry_attrs['ipanttrustpartner'] = [options['ipanttrustpartner']]
- return dn
-
-@register()
-class trustdomain_del(LDAPDelete):
- __doc__ = _('Remove infromation about the domain associated with the trust.')
-
- msg_summary = _('Removed information about the trusted domain "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- verify_samba_component_presence(ldap, self.api)
-
- # Note that pre-/post- callback handling for LDAPDelete is causing pre_callback
- # to always receive empty keys. We need to catch the case when root domain is being deleted
-
- for domain in keys[1]:
- # Fetch the trust to verify that the entered domain is trusted
- self.api.Command.trust_show(domain)
-
- if keys[0].lower() == domain:
- raise errors.ValidationError(name='domain',
- error=_("cannot delete root domain of the trust, "
- "use trust-del to delete the trust itself"))
- try:
- res = self.api.Command.trustdomain_enable(keys[0], domain)
- except errors.AlreadyActive:
- pass
-
- result = super(trustdomain_del, self).execute(*keys, **options)
- result['value'] = pkey_to_value(keys[1], options)
- return result
-
-
-def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options):
- trust_name = trust_entry['cn'][0]
- # We want to use Kerberos if we have admin credentials even with SMB calls
- # as eventually use of NTLMSSP will be deprecated for trusted domain operations
- # If admin credentials are missing, 'creds' will be None and fetch_domains
- # will use HTTP/ipa.master@IPA.REALM principal, e.g. Kerberos authentication
- # as well.
- creds = generate_creds(trustinstance, style=CRED_STYLE_KERBEROS, **options)
- server = options.get('realm_server', None)
- domains = ipaserver.dcerpc.fetch_domains(myapi,
- trustinstance.local_flatname,
- trust_name, creds=creds, server=server)
- return domains
-
-def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **options):
- result = []
- if not domains:
- return result
-
- trust_name = trust_entry['cn'][0]
- # trust range must exist by the time add_new_domains_from_trust is called
- range_name = trust_name.upper() + '_id_range'
- old_range = myapi.Command.idrange_show(range_name, raw=True)['result']
- idrange_type = old_range['iparangetype'][0]
-
- for dom in domains:
- dom['trust_type'] = u'ad'
- try:
- name = dom['cn']
- del dom['cn']
- if 'all' in options:
- dom['all'] = options['all']
- if 'raw' in options:
- dom['raw'] = options['raw']
-
- res = myapi.Command.trustdomain_add(trust_name, name, **dom)
- result.append(res['result'])
-
- if idrange_type != u'ipa-ad-trust-posix':
- range_name = name.upper() + '_id_range'
- dom['range_type'] = u'ipa-ad-trust'
- add_range(myapi, trustinstance, range_name, dom['ipanttrusteddomainsid'],
- trust_name, name, **dom)
- except errors.DuplicateEntry:
- # Ignore updating duplicate entries
- pass
- return result
-
-@register()
-class trust_fetch_domains(LDAPRetrieve):
- __doc__ = _('Refresh list of the domains associated with the trust')
-
- has_output = output.standard_list_of_entries
- takes_options = LDAPRetrieve.takes_options + (
- Str('realm_server?',
- cli_name='server',
- label=_('Domain controller for the Active Directory domain (optional)'),
- ),
- )
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- verify_samba_component_presence(ldap, self.api)
-
- trust = self.api.Command.trust_show(keys[0], raw=True)['result']
-
- result = dict()
- result['result'] = []
- result['count'] = 0
- result['truncated'] = False
-
- # For one-way trust fetch over DBus. we don't get the list in this case.
- if int(trust['ipanttrustdirection'][0]) != TRUST_BIDIRECTIONAL:
- fetch_trusted_domains_over_dbus(self.api, self.log, keys[0])
- result['summary'] = unicode(_('List of trust domains successfully refreshed. Use trustdomain-find command to list them.'))
- return result
-
- trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
- if not trustinstance.configured:
- raise errors.NotFound(
- name=_('AD Trust setup'),
- reason=_(
- 'Cannot perform join operation without own domain '
- 'configured. Make sure you have run ipa-adtrust-install '
- 'on the IPA server first'
- )
- )
- res = fetch_domains_from_trust(self.api, trustinstance, trust, **options)
- domains = add_new_domains_from_trust(self.api, trustinstance, trust, res, **options)
-
- if len(domains) > 0:
- result['summary'] = unicode(_('List of trust domains successfully refreshed'))
- else:
- result['summary'] = unicode(_('No new trust domains were found'))
-
- result['result'] = domains
- result['count'] = len(domains)
- return result
-
-
-@register()
-class trustdomain_enable(LDAPQuery):
- __doc__ = _('Allow use of IPA resources by the domain of the trust')
-
- has_output = output.standard_value
- msg_summary = _('Enabled trust domain "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- verify_samba_component_presence(ldap, self.api)
-
- if keys[0].lower() == keys[1].lower():
- raise errors.ValidationError(name='domain',
- error=_("Root domain of the trust is always enabled for the existing trust"))
- try:
- trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
- trust_entry = ldap.get_entry(trust_dn)
- except errors.NotFound:
- self.api.Object[self.obj.parent_object].handle_not_found(keys[0])
-
- dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
- try:
- entry = ldap.get_entry(dn)
- sid = entry['ipanttrusteddomainsid'][0]
- if sid in trust_entry['ipantsidblacklistincoming']:
- trust_entry['ipantsidblacklistincoming'].remove(sid)
- ldap.update_entry(trust_entry)
- # Force MS-PAC cache re-initialization on KDC side
- domval = ipaserver.dcerpc.DomainValidator(self.api)
- (ccache_name, principal) = domval.kinit_as_http(keys[0])
- else:
- raise errors.AlreadyActive()
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[1], options),
- )
-
-
-@register()
-class trustdomain_disable(LDAPQuery):
- __doc__ = _('Disable use of IPA resources by the domain of the trust')
-
- has_output = output.standard_value
- msg_summary = _('Disabled trust domain "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.api.Backend.ldap2
- verify_samba_component_presence(ldap, self.api)
-
- if keys[0].lower() == keys[1].lower():
- raise errors.ValidationError(name='domain',
- error=_("cannot disable root domain of the trust, use trust-del to delete the trust itself"))
- try:
- trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
- trust_entry = ldap.get_entry(trust_dn)
- except errors.NotFound:
- self.api.Object[self.obj.parent_object].handle_not_found(keys[0])
-
- dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
- try:
- entry = ldap.get_entry(dn)
- sid = entry['ipanttrusteddomainsid'][0]
- if not (sid in trust_entry['ipantsidblacklistincoming']):
- trust_entry['ipantsidblacklistincoming'].append(sid)
- ldap.update_entry(trust_entry)
- # Force MS-PAC cache re-initialization on KDC side
- domval = ipaserver.dcerpc.DomainValidator(self.api)
- (ccache_name, principal) = domval.kinit_as_http(keys[0])
- else:
- raise errors.AlreadyInactive()
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[1], options),
- )
-
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
deleted file mode 100644
index adc59fcba..000000000
--- a/ipalib/plugins/user.py
+++ /dev/null
@@ -1,1151 +0,0 @@
-# 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/>.
-
-import time
-from time import gmtime, strftime
-import posixpath
-import os
-
-import six
-
-from ipalib import api
-from ipalib import errors
-from ipalib import Bool, Flag, Str
-from .baseuser import (
- baseuser,
- baseuser_add,
- baseuser_del,
- baseuser_mod,
- baseuser_find,
- baseuser_show,
- NO_UPG_MAGIC,
- UPG_DEFINITION_DN,
- baseuser_output_params,
- status_baseuser_output_params,
- baseuser_pwdchars,
- validate_nsaccountlock,
- convert_nsaccountlock,
- fix_addressbook_permission_bindrule,
- baseuser_add_manager,
- baseuser_remove_manager)
-from .idviews import remove_ipaobject_overrides
-from ipalib.plugable import Registry
-from .baseldap import (
- pkey_to_value,
- LDAPCreate,
- LDAPSearch,
- LDAPQuery,
- LDAPMultiQuery,
- LDAPAddAttribute,
- LDAPRemoveAttribute)
-from . import baseldap
-from ipalib.request import context
-from ipalib import _, ngettext
-from ipalib import output
-from ipaplatform.paths import paths
-from ipapython.dn import DN
-from ipapython.ipautil import ipa_generate_password
-from ipalib.capabilities import client_has_capability
-
-if api.env.in_server:
- from ipaserver.plugins.ldap2 import ldap2
-
-if six.PY3:
- unicode = str
-
-__doc__ = _("""
-Users
-
-Manage user entries. All users are POSIX users.
-
-IPA supports a wide range of username formats, but you need to be aware of any
-restrictions that may apply to your particular environment. For example,
-usernames that start with a digit or usernames that exceed a certain length
-may cause problems for some UNIX systems.
-Use 'ipa config-mod' to change the username format allowed by IPA tools.
-
-Disabling a user account prevents that user from obtaining new Kerberos
-credentials. It does not invalidate any credentials that have already
-been issued.
-
-Password management is not a part of this module. For more information
-about this topic please see: ipa help passwd
-
-Account lockout on password failure happens per IPA master. The user-status
-command can be used to identify which master the user is locked out on.
-It is on that master the administrator must unlock the user.
-
-EXAMPLES:
-
- Add a new user:
- ipa user-add --first=Tim --last=User --password tuser1
-
- Find all users whose entries include the string "Tim":
- ipa user-find Tim
-
- Find all users with "Tim" as the first name:
- ipa user-find --first=Tim
-
- Disable a user account:
- ipa user-disable tuser1
-
- Enable a user account:
- ipa user-enable tuser1
-
- Delete a user:
- ipa user-del tuser1
-""")
-
-register = Registry()
-
-
-user_output_params = baseuser_output_params
-
-status_output_params = status_baseuser_output_params
-
-
-def check_protected_member(user, protected_group_name=u'admins'):
- '''
- Ensure the last enabled member of a protected group cannot be deleted or
- disabled by raising LastMemberError.
- '''
-
- # Get all users in the protected group
- result = api.Command.user_find(in_group=protected_group_name)
-
- # Build list of users in the protected group who are enabled
- result = result['result']
- enabled_users = [entry['uid'][0] for entry in result if not entry['nsaccountlock']]
-
- # If the user is the last enabled user raise LastMemberError exception
- if enabled_users == [user]:
- raise errors.LastMemberError(key=user, label=_(u'group'),
- container=protected_group_name)
-
-@register()
-class user(baseuser):
- """
- User object.
- """
-
- container_dn = baseuser.active_container_dn
- label = _('Users')
- label_singular = _('User')
- object_name = _('user')
- object_name_plural = _('users')
- managed_permissions = {
- 'System: Read User Standard Attributes': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'sn', 'description', 'title', 'uid',
- 'displayname', 'givenname', 'initials', 'manager', 'gecos',
- 'gidnumber', 'homedirectory', 'loginshell', 'uidnumber',
- 'ipantsecurityidentifier'
- },
- },
- 'System: Read User Addressbook Attributes': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'seealso', 'telephonenumber',
- 'facsimiletelephonenumber', 'l', 'ou', 'st', 'postalcode', 'street',
- 'destinationindicator', 'internationalisdnnumber',
- 'physicaldeliveryofficename', 'postaladdress', 'postofficebox',
- 'preferreddeliverymethod', 'registeredaddress',
- 'teletexterminalidentifier', 'telexnumber', 'x121address',
- 'carlicense', 'departmentnumber', 'employeenumber',
- 'employeetype', 'preferredlanguage', 'mail', 'mobile', 'pager',
- 'audio', 'businesscategory', 'homephone', 'homepostaladdress',
- 'jpegphoto', 'labeleduri', 'o', 'photo', 'roomnumber',
- 'secretary', 'usercertificate',
- 'usersmimecertificate', 'x500uniqueidentifier',
- 'inetuserhttpurl', 'inetuserstatus',
- },
- 'fixup_function': fix_addressbook_permission_bindrule,
- },
- 'System: Read User IPA Attributes': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'ipauniqueid', 'ipasshpubkey', 'ipauserauthtype', 'userclass',
- },
- 'fixup_function': fix_addressbook_permission_bindrule,
- },
- 'System: Read User Kerberos Attributes': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases',
- 'krbprincipalexpiration', 'krbpasswordexpiration',
- 'krblastpwdchange', 'nsaccountlock', 'krbprincipaltype',
- },
- },
- 'System: Read User Kerberos Login Attributes': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'krblastsuccessfulauth', 'krblastfailedauth',
- 'krblastpwdchange', 'krblastadminunlock',
- 'krbloginfailedcount', 'krbpwdpolicyreference',
- 'krbticketpolicyreference', 'krbupenabled',
- },
- 'default_privileges': {'User Administrators'},
- },
- 'System: Read User Membership': {
- 'replaces_global_anonymous_aci': True,
- 'ipapermbindruletype': 'all',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'memberof',
- },
- },
- 'System: Read UPG Definition': {
- # Required for adding users
- 'replaces_global_anonymous_aci': True,
- 'non_object': True,
- 'ipapermlocation': UPG_DEFINITION_DN,
- 'ipapermtarget': UPG_DEFINITION_DN,
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {'*'},
- 'default_privileges': {'User Administrators'},
- },
- 'System: Add Users': {
- 'ipapermright': {'add'},
- 'replaces': [
- '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Users";allow (add) groupdn = "ldap:///cn=Add Users,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'User Administrators'},
- },
- 'System: Add User to default group': {
- 'non_object': True,
- 'ipapermright': {'write'},
- 'ipapermlocation': DN(api.env.container_group, api.env.basedn),
- 'ipapermtarget': DN('cn=ipausers', api.env.container_group,
- api.env.basedn),
- 'ipapermdefaultattr': {'member'},
- 'replaces': [
- '(targetattr = "member")(target = "ldap:///cn=ipausers,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add user to default group";allow (write) groupdn = "ldap:///cn=Add user to default group,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'User Administrators'},
- },
- 'System: Change User password': {
- 'ipapermright': {'write'},
- 'ipapermtargetfilter': [
- '(objectclass=posixaccount)',
- '(!(memberOf=%s))' % DN('cn=admins',
- api.env.container_group,
- api.env.basedn),
- ],
- 'ipapermdefaultattr': {
- 'krbprincipalkey', 'passwordhistory', 'sambalmpassword',
- 'sambantpassword', 'userpassword'
- },
- 'replaces': [
- '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetfilter = "(!(memberOf=cn=admins,cn=groups,cn=accounts,$SUFFIX))")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(targetattr = "userpassword || krbprincipalkey || sambalmpassword || sambantpassword || passwordhistory")(version 3.0;acl "permission:Change a user password";allow (write) groupdn = "ldap:///cn=Change a user password,cn=permissions,cn=pbac,$SUFFIX";)',
- '(targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Windows PassSync service can write passwords"; allow (write) userdn="ldap:///uid=passsync,cn=sysaccounts,cn=etc,$SUFFIX";)',
- ],
- 'default_privileges': {
- 'User Administrators',
- 'Modify Users and Reset passwords',
- 'PassSync Service',
- },
- },
- 'System: Manage User SSH Public Keys': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'ipasshpubkey'},
- 'replaces': [
- '(targetattr = "ipasshpubkey")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage User SSH Public Keys";allow (write) groupdn = "ldap:///cn=Manage User SSH Public Keys,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'User Administrators'},
- },
- 'System: Manage User Certificates': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {'usercertificate'},
- 'default_privileges': {
- 'User Administrators',
- 'Modify Users and Reset passwords',
- },
- },
- 'System: Modify Users': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'businesscategory', 'carlicense', 'cn', 'departmentnumber',
- 'description', 'displayname', 'employeetype',
- 'employeenumber', 'facsimiletelephonenumber',
- 'gecos', 'givenname', 'homephone', 'inetuserhttpurl',
- 'initials', 'l', 'labeleduri', 'loginshell', 'manager', 'mail',
- 'mepmanagedentry', 'mobile', 'objectclass', 'ou', 'pager',
- 'postalcode', 'roomnumber', 'secretary', 'seealso', 'sn', 'st',
- 'street', 'telephonenumber', 'title', 'userclass',
- 'preferredlanguage',
- },
- 'replaces': [
- '(targetattr = "givenname || sn || cn || displayname || title || initials || loginshell || gecos || homephone || mobile || pager || facsimiletelephonenumber || telephonenumber || street || roomnumber || l || st || postalcode || manager || secretary || description || carlicense || labeleduri || inetuserhttpurl || seealso || employeetype || businesscategory || ou || mepmanagedentry || objectclass")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Users";allow (write) groupdn = "ldap:///cn=Modify Users,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {
- 'User Administrators',
- 'Modify Users and Reset passwords',
- },
- },
- 'System: Remove Users': {
- 'ipapermright': {'delete'},
- 'replaces': [
- '(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Users";allow (delete) groupdn = "ldap:///cn=Remove Users,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'User Administrators'},
- },
- 'System: Unlock User': {
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'krblastadminunlock', 'krbloginfailedcount', 'nsaccountlock',
- },
- 'replaces': [
- '(targetattr = "krbLastAdminUnlock || krbLoginFailedCount")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Unlock user accounts";allow (write) groupdn = "ldap:///cn=Unlock user accounts,cn=permissions,cn=pbac,$SUFFIX";)',
- ],
- 'default_privileges': {'User Administrators'},
- },
- 'System: Read User Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=users', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber',
- 'homedirectory', 'loginshell',
- },
- },
- 'System: Read User Views Compat Tree': {
- 'non_object': True,
- 'ipapermbindruletype': 'anonymous',
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN('cn=users', 'cn=*', 'cn=views', 'cn=compat', api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber',
- 'homedirectory', 'loginshell',
- },
- },
- 'System: Read User NT Attributes': {
- 'ipapermbindruletype': 'permission',
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'ntuserdomainid', 'ntuniqueid', 'ntuseracctexpires',
- 'ntusercodepage', 'ntuserdeleteaccount', 'ntuserlastlogoff',
- 'ntuserlastlogon',
- },
- 'default_privileges': {'PassSync Service'},
- },
- }
-
- takes_params = baseuser.takes_params + (
- Bool('nsaccountlock?',
- label=_('Account disabled'),
- flags=['no_option'],
- ),
- Bool('preserved?',
- label=_('Preserved user'),
- default=False,
- flags=['virtual_attribute', 'no_create', 'no_update'],
- ),
- )
-
- def get_either_dn(self, *keys, **options):
- '''
- Returns the DN of a user
- The user can be active (active container) or delete (delete container)
- If the user does not exist, returns the Active user DN
- '''
- ldap = self.backend
- # Check that this value is a Active user
- try:
- active_dn = self.get_dn(*keys, **options)
- ldap.get_entry(active_dn, ['dn'])
-
- # The Active user exists
- dn = active_dn
- except errors.NotFound:
- # Check that this value is a Delete user
- delete_dn = DN(active_dn[0], self.delete_container_dn, api.env.basedn)
- try:
- ldap.get_entry(delete_dn, ['dn'])
-
- # The Delete user exists
- dn = delete_dn
- except errors.NotFound:
- # The user is neither Active/Delete -> returns that Active DN
- dn = active_dn
-
- return dn
-
- def _normalize_manager(self, manager):
- """
- Given a userid verify the user's existence and return the dn.
- """
- return super(user, self).normalize_manager(manager, self.active_container_dn)
-
- def get_preserved_attribute(self, entry, options):
- if options.get('raw', False):
- return
- delete_container_dn = DN(self.delete_container_dn, api.env.basedn)
- if entry.dn.endswith(delete_container_dn):
- entry['preserved'] = True
- elif options.get('all', False):
- entry['preserved'] = False
-
-
-@register()
-class user_add(baseuser_add):
- __doc__ = _('Add a new user.')
-
- msg_summary = _('Added user "%(value)s"')
-
- has_output_params = baseuser_add.has_output_params + user_output_params
-
- takes_options = LDAPCreate.takes_options + (
- Flag('noprivate',
- cli_name='noprivate',
- doc=_('Don\'t create user private group'),
- ),
- )
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = self.obj.get_either_dn(*keys, **options)
- if not options.get('noprivate', False):
- try:
- # The Managed Entries plugin will allow a user to be created
- # even if a group has a duplicate name. This would leave a user
- # without a private group. Check for both the group and the user.
- self.api.Object['group'].get_dn_if_exists(keys[-1])
- try:
- self.api.Command['user_show'](keys[-1])
- self.obj.handle_duplicate_entry(*keys)
- except errors.NotFound:
- raise errors.ManagedGroupExistsError(group=keys[-1])
- except errors.NotFound:
- pass
- else:
- # we don't want an user private group to be created for this user
- # add NO_UPG_MAGIC description attribute to let the DS plugin know
- entry_attrs.setdefault('description', [])
- entry_attrs['description'].append(NO_UPG_MAGIC)
-
- entry_attrs.setdefault('uidnumber', baseldap.DNA_MAGIC)
-
- if not client_has_capability(
- options['version'], 'optional_uid_params'):
- # https://fedorahosted.org/freeipa/ticket/2886
- # Old clients say 999 (OLD_DNA_MAGIC) when they really mean
- # "assign a value dynamically".
- OLD_DNA_MAGIC = 999
- if entry_attrs.get('uidnumber') == OLD_DNA_MAGIC:
- entry_attrs['uidnumber'] = baseldap.DNA_MAGIC
- if entry_attrs.get('gidnumber') == OLD_DNA_MAGIC:
- entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
-
- validate_nsaccountlock(entry_attrs)
- config = ldap.get_ipa_config()
- if 'ipamaxusernamelength' in config:
- if len(keys[-1]) > int(config.get('ipamaxusernamelength')[0]):
- raise errors.ValidationError(
- name=self.obj.primary_key.cli_name,
- error=_('can be at most %(len)d characters') % dict(
- len = int(config.get('ipamaxusernamelength')[0])
- )
- )
- default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
- entry_attrs.setdefault('loginshell', default_shell)
- # hack so we can request separate first and last name in CLI
- full_name = '%s %s' % (entry_attrs['givenname'], entry_attrs['sn'])
- entry_attrs.setdefault('cn', full_name)
- if 'homedirectory' not in entry_attrs:
- # get home's root directory from config
- homes_root = config.get('ipahomesrootdir', [paths.HOME_DIR])[0]
- # build user's home directory based on his uid
- entry_attrs['homedirectory'] = posixpath.join(homes_root, keys[-1])
- entry_attrs.setdefault('krbprincipalname', '%s@%s' % (entry_attrs['uid'], api.env.realm))
-
- if entry_attrs.get('gidnumber') is None:
- # gidNumber wasn't specified explicity, find out what it should be
- if not options.get('noprivate', False) and ldap.has_upg():
- # User Private Groups - uidNumber == gidNumber
- entry_attrs['gidnumber'] = entry_attrs['uidnumber']
- else:
- # we're adding new users to a default group, get its gidNumber
- # get default group name from config
- def_primary_group = config.get('ipadefaultprimarygroup')
- group_dn = self.api.Object['group'].get_dn(def_primary_group)
- try:
- group_attrs = ldap.get_entry(group_dn, ['gidnumber'])
- except errors.NotFound:
- error_msg = _('Default group for new users not found')
- raise errors.NotFound(reason=error_msg)
- if 'gidnumber' not in group_attrs:
- error_msg = _('Default group for new users is not POSIX')
- raise errors.NotFound(reason=error_msg)
- entry_attrs['gidnumber'] = group_attrs['gidnumber']
-
- if 'userpassword' not in entry_attrs and options.get('random'):
- entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
- # save the password so it can be displayed in post_callback
- setattr(context, 'randompassword', entry_attrs['userpassword'])
-
- if 'mail' in entry_attrs:
- entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'], config)
- else:
- # No e-mail passed in. If we have a default e-mail domain set
- # then we'll add it automatically.
- defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
- if defaultdomain:
- entry_attrs['mail'] = self.obj.normalize_and_validate_email(keys[-1], config)
-
- if 'manager' in entry_attrs:
- entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], self.obj.active_container_dn)
-
- if 'userclass' in entry_attrs and \
- 'ipauser' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('ipauser')
-
- if 'ipauserauthtype' in entry_attrs and \
- 'ipauserauthtypeclass' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('ipauserauthtypeclass')
-
- rcl = entry_attrs.get('ipatokenradiusconfiglink', None)
- if rcl:
- if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
- entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
-
- answer = self.api.Object['radiusproxy'].get_dn_if_exists(rcl)
- entry_attrs['ipatokenradiusconfiglink'] = answer
-
- self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
- **options)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
- config = ldap.get_ipa_config()
- # add the user we just created into the default primary group
- def_primary_group = config.get('ipadefaultprimarygroup')
- group_dn = self.api.Object['group'].get_dn(def_primary_group)
-
- # if the user is already a member of default primary group,
- # do not raise error
- # this can happen if automember rule or default group is set
- try:
- ldap.add_entry_to_group(dn, group_dn)
- except errors.AlreadyGroupMember:
- pass
-
- # delete description attribute NO_UPG_MAGIC if present
- if options.get('noprivate', False):
- if not options.get('all', False):
- desc_attr = ldap.get_entry(dn, ['description'])
- entry_attrs.update(desc_attr)
- if 'description' in entry_attrs and NO_UPG_MAGIC in entry_attrs['description']:
- entry_attrs['description'].remove(NO_UPG_MAGIC)
- kw = {'setattr': unicode('description=%s' % ','.join(entry_attrs['description']))}
- try:
- self.api.Command['user_mod'](keys[-1], **kw)
- except (errors.EmptyModlist, errors.NotFound):
- pass
-
- # Fetch the entry again to update memberof, mep data, etc updated
- # at the end of the transaction.
- newentry = ldap.get_entry(dn, ['*'])
- entry_attrs.update(newentry)
-
- if options.get('random', False):
- try:
- entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
- except AttributeError:
- # if both randompassword and userpassword options were used
- pass
-
- self.obj.get_preserved_attribute(entry_attrs, options)
-
- self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
-
- return dn
-
-
-@register()
-class user_del(baseuser_del):
- __doc__ = _('Delete a user.')
-
- msg_summary = _('Deleted user "%(value)s"')
-
- takes_options = baseuser_del.takes_options + (
- Bool('preserve?',
- exclude='cli',
- ),
- )
-
- def _preserve_user(self, pkey, delete_container, **options):
- assert isinstance(delete_container, DN)
-
- dn = self.obj.get_either_dn(pkey, **options)
- delete_dn = DN(dn[0], delete_container)
- ldap = self.obj.backend
- self.log.debug("preserve move %s -> %s" % (dn, delete_dn))
-
- if dn.endswith(delete_container):
- raise errors.ExecutionError(
- _('%s: user is already preserved' % pkey)
- )
- # Check that this value is a Active user
- try:
- original_entry_attrs = self._exc_wrapper(
- pkey, options, ldap.get_entry)(dn, ['dn'])
- except errors.NotFound:
- self.obj.handle_not_found(pkey)
-
- for callback in self.get_callbacks('pre'):
- dn = callback(self, ldap, dn, pkey, **options)
- assert isinstance(dn, DN)
-
- # start to move the entry to Delete container
- self._exc_wrapper(pkey, options, ldap.move_entry)(dn, delete_dn,
- del_old=True)
-
- # Then clear the credential attributes
- attrs_to_clear = ['krbPrincipalKey', 'krbLastPwdChange',
- 'krbPasswordExpiration', 'userPassword']
-
- entry_attrs = self._exc_wrapper(pkey, options, ldap.get_entry)(
- delete_dn, attrs_to_clear)
-
- clearedCredential = False
- for attr in attrs_to_clear:
- if attr.lower() in entry_attrs:
- del entry_attrs[attr]
- clearedCredential = True
- if clearedCredential:
- self._exc_wrapper(pkey, options, ldap.update_entry)(entry_attrs)
-
- # Then restore some original entry attributes
- attrs_to_restore = ['secretary', 'managedby', 'manager', 'ipauniqueid',
- 'uidnumber', 'gidnumber', 'passwordHistory']
-
- entry_attrs = self._exc_wrapper(
- pkey, options, ldap.get_entry)(delete_dn, attrs_to_restore)
-
- restoreAttr = False
- for attr in attrs_to_restore:
- if ((attr.lower() in original_entry_attrs) and
- not (attr.lower() in entry_attrs)):
- restoreAttr = True
- entry_attrs[attr.lower()] = original_entry_attrs[attr.lower()]
- if restoreAttr:
- self._exc_wrapper(pkey, options, ldap.update_entry)(entry_attrs)
-
- def pre_callback(self, ldap, dn, *keys, **options):
- dn = self.obj.get_either_dn(*keys, **options)
-
- # For User life Cycle: user-del is a common plugin
- # command to delete active user (active container) and
- # delete user (delete container).
- # If the target entry is a Delete entry, skip the orphaning/removal
- # of OTP tokens.
- check_protected_member(keys[-1])
-
- if not options.get('preserve', False):
- # Remove any ID overrides tied with this user
- try:
- remove_ipaobject_overrides(self.obj.backend, self.obj.api, dn)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
-
- if dn.endswith(DN(self.obj.delete_container_dn, api.env.basedn)):
- return dn
-
- # Delete all tokens owned and managed by this user.
- # Orphan all tokens owned but not managed by this user.
- owner = self.api.Object.user.get_primary_key_from_dn(dn)
- results = self.api.Command.otptoken_find(
- ipatokenowner=owner, no_members=False)['result']
- for token in results:
- orphan = not [x for x in token.get('managedby_user', []) if x == owner]
- token = self.api.Object.otptoken.get_primary_key_from_dn(token['dn'])
- if orphan:
- self.api.Command.otptoken_mod(token, ipatokenowner=None)
- else:
- self.api.Command.otptoken_del(token)
-
- return dn
-
- def execute(self, *keys, **options):
-
- # We are going to permanent delete or the user is already in the delete container.
- delete_container = DN(self.obj.delete_container_dn, self.api.env.basedn)
-
- # The user to delete is active and there is no 'no_preserve' option
- if options.get('preserve', False):
- failed = []
- preserved = []
- for pkey in keys[-1]:
- try:
- self._preserve_user(pkey, delete_container, **options)
- preserved.append(pkey_to_value(pkey, options))
- except Exception:
- if not options.get('continue', False):
- raise
- failed.append(pkey_to_value(pkey, options))
-
- val = dict(result=dict(failed=failed), value=preserved)
- return val
- else:
- return super(user_del, self).execute(*keys, **options)
-
-
-@register()
-class user_mod(baseuser_mod):
- __doc__ = _('Modify a user.')
-
- msg_summary = _('Modified user "%(value)s"')
-
- has_output_params = baseuser_mod.has_output_params + user_output_params
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
- dn = self.obj.get_either_dn(*keys, **options)
- self.pre_common_callback(ldap, dn, entry_attrs, attrs_list, *keys,
- **options)
- validate_nsaccountlock(entry_attrs)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
- self.obj.get_preserved_attribute(entry_attrs, options)
- return dn
-
-
-@register()
-class user_find(baseuser_find):
- __doc__ = _('Search for users.')
-
- member_attributes = ['memberof']
- has_output_params = baseuser_find.has_output_params + user_output_params
-
- msg_summary = ngettext(
- '%(count)d user matched', '%(count)d users matched', 0
- )
-
- takes_options = LDAPSearch.takes_options + (
- Flag('whoami',
- label=_('Self'),
- doc=_('Display user record for current Kerberos principal'),
- ),
- )
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options):
- assert isinstance(base_dn, DN)
- self.pre_common_callback(ldap, filter, attrs_list, base_dn, scope,
- *keys, **options)
-
- if options.get('whoami'):
- return ("(&(objectclass=posixaccount)(krbprincipalname=%s))"%\
- getattr(context, 'principal'), base_dn, scope)
-
- preserved = options.get('preserved', False)
- if preserved is None:
- base_dn = self.api.env.basedn
- scope = ldap.SCOPE_SUBTREE
- elif preserved:
- base_dn = DN(self.obj.delete_container_dn, self.api.env.basedn)
- else:
- base_dn = DN(self.obj.active_container_dn, self.api.env.basedn)
-
- return (filter, base_dn, scope)
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- if options.get('pkey_only', False):
- return truncated
-
- if options.get('preserved', False) is None:
- base_dns = (
- DN(self.obj.active_container_dn, self.api.env.basedn),
- DN(self.obj.delete_container_dn, self.api.env.basedn),
- )
- entries[:] = [e for e in entries
- if any(e.dn.endswith(bd) for bd in base_dns)]
-
- self.post_common_callback(ldap, entries, lockout=False, **options)
- for entry in entries:
- self.obj.get_preserved_attribute(entry, options)
-
- return truncated
-
-
-@register()
-class user_show(baseuser_show):
- __doc__ = _('Display information about a user.')
-
- has_output_params = baseuser_show.has_output_params + user_output_params
- takes_options = baseuser_show.takes_options + (
- Str('out?',
- doc=_('file to store certificate in'),
- ),
- )
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- dn = self.obj.get_either_dn(*keys, **options)
- self.pre_common_callback(ldap, dn, attrs_list, *keys, **options)
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- convert_nsaccountlock(entry_attrs)
- self.post_common_callback(ldap, dn, entry_attrs, *keys, **options)
- self.obj.get_preserved_attribute(entry_attrs, options)
- return dn
-
-
-@register()
-class user_undel(LDAPQuery):
- __doc__ = _('Undelete a delete user account.')
-
- has_output = output.standard_value
- msg_summary = _('Undeleted user account "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- # First check that the user exists and is a delete one
- delete_dn = self.obj.get_either_dn(*keys, **options)
- try:
- entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(delete_dn)
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if delete_dn.endswith(DN(self.obj.active_container_dn,
- api.env.basedn)):
- raise errors.InvocationError(
- message=_('user "%s" is already active') % keys[-1])
-
- active_dn = DN(delete_dn[0], self.obj.active_container_dn, api.env.basedn)
-
- # start to move the entry to the Active container
- self._exc_wrapper(keys, options, ldap.move_entry)(delete_dn, active_dn, del_old=True)
-
- # add the user we just undelete into the default primary group
- config = ldap.get_ipa_config()
- def_primary_group = config.get('ipadefaultprimarygroup')
- group_dn = self.api.Object['group'].get_dn(def_primary_group)
-
- # if the user is already a member of default primary group,
- # do not raise error
- # this can happen if automember rule or default group is set
- try:
- ldap.add_entry_to_group(active_dn, group_dn)
- except errors.AlreadyGroupMember:
- pass
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
-
-@register()
-class user_stage(LDAPMultiQuery):
- __doc__ = _('Move deleted user into staged area')
-
- has_output = output.standard_multi_delete
- msg_summary = _('Staged user account "%(value)s"')
-
- def execute(self, *keys, **options):
- staged = []
- failed = []
-
- for key in keys[-1]:
- single_keys = keys[:-1] + (key,)
- multi_keys = keys[:-1] + ((key,),)
-
- user = self.api.Command.user_show(*single_keys, all=True)['result']
- new_options = {}
- for param in self.api.Command.stageuser_add.options():
- try:
- value = user[param.name]
- except KeyError:
- continue
- if param.multivalue and not isinstance(value, (list, tuple)):
- value = [value]
- elif not param.multivalue and isinstance(value, (list, tuple)):
- value = value[0]
- new_options[param.name] = value
-
- try:
- self.api.Command.stageuser_add(*single_keys, **new_options)
- try:
- self.api.Command.user_del(*multi_keys, preserve=False)
- except errors.ExecutionError:
- self.api.Command.stageuser_del(*multi_keys)
- raise
- except errors.ExecutionError:
- if not options['continue']:
- raise
- failed.append(key)
- else:
- staged.append(key)
-
- return dict(
- result=dict(
- failed=pkey_to_value(failed, options),
- ),
- value=pkey_to_value(staged, options),
- )
-
-
-@register()
-class user_disable(LDAPQuery):
- __doc__ = _('Disable a user account.')
-
- has_output = output.standard_value
- msg_summary = _('Disabled user account "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- check_protected_member(keys[-1])
-
- dn = self.obj.get_either_dn(*keys, **options)
- ldap.deactivate_entry(dn)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
-
-@register()
-class user_enable(LDAPQuery):
- __doc__ = _('Enable a user account.')
-
- has_output = output.standard_value
- has_output_params = LDAPQuery.has_output_params + user_output_params
- msg_summary = _('Enabled user account "%(value)s"')
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
-
- dn = self.obj.get_either_dn(*keys, **options)
-
- ldap.activate_entry(dn)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
-
-@register()
-class user_unlock(LDAPQuery):
- __doc__ = _("""
- Unlock a user account
-
- An account may become locked if the password is entered incorrectly too
- many times within a specific time period as controlled by password
- policy. A locked account is a temporary condition and may be unlocked by
- an administrator.""")
-
- has_output = output.standard_value
- msg_summary = _('Unlocked account "%(value)s"')
-
- def execute(self, *keys, **options):
- dn = self.obj.get_either_dn(*keys, **options)
- entry = self.obj.backend.get_entry(
- dn, ['krbLastAdminUnlock', 'krbLoginFailedCount'])
-
- entry['krbLastAdminUnlock'] = [strftime("%Y%m%d%H%M%SZ", gmtime())]
- entry['krbLoginFailedCount'] = ['0']
-
- self.obj.backend.update_entry(entry)
-
- return dict(
- result=True,
- value=pkey_to_value(keys[0], options),
- )
-
-
-@register()
-class user_status(LDAPQuery):
- __doc__ = _("""
- Lockout status of a user account
-
- An account may become locked if the password is entered incorrectly too
- many times within a specific time period as controlled by password
- policy. A locked account is a temporary condition and may be unlocked by
- an administrator.
-
- This connects to each IPA master and displays the lockout status on
- each one.
-
- To determine whether an account is locked on a given server you need
- to compare the number of failed logins and the time of the last failure.
- For an account to be locked it must exceed the maxfail failures within
- the failinterval duration as specified in the password policy associated
- with the user.
-
- The failed login counter is modified only when a user attempts a log in
- so it is possible that an account may appear locked but the last failed
- login attempt is older than the lockouttime of the password policy. This
- means that the user may attempt a login again. """)
-
- has_output = output.standard_list_of_entries
- has_output_params = LDAPSearch.has_output_params + status_output_params
-
- def execute(self, *keys, **options):
- ldap = self.obj.backend
- dn = self.obj.get_either_dn(*keys, **options)
- attr_list = ['krbloginfailedcount', 'krblastsuccessfulauth', 'krblastfailedauth', 'nsaccountlock']
-
- disabled = False
- masters = []
- # Get list of masters
- try:
- (masters, truncated) = ldap.find_entries(
- None, ['*'], DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn),
- ldap.SCOPE_ONELEVEL
- )
- except errors.NotFound:
- # If this happens we have some pretty serious problems
- self.error('No IPA masters found!')
-
- entries = []
- count = 0
- for master in masters:
- host = master['cn'][0]
- if host == api.env.host:
- other_ldap = self.obj.backend
- else:
- other_ldap = ldap2(self.api, ldap_uri='ldap://%s' % host)
- try:
- other_ldap.connect(ccache=os.environ['KRB5CCNAME'])
- except Exception as e:
- self.error("user_status: Connecting to %s failed with %s" % (host, str(e)))
- newresult = {'dn': dn}
- newresult['server'] = _("%(host)s failed: %(error)s") % dict(host=host, error=str(e))
- entries.append(newresult)
- count += 1
- continue
- try:
- entry = other_ldap.get_entry(dn, attr_list)
- newresult = {'dn': dn}
- for attr in ['krblastsuccessfulauth', 'krblastfailedauth']:
- newresult[attr] = entry.get(attr, [u'N/A'])
- newresult['krbloginfailedcount'] = entry.get('krbloginfailedcount', u'0')
- if not options.get('raw', False):
- for attr in ['krblastsuccessfulauth', 'krblastfailedauth']:
- try:
- if newresult[attr][0] == u'N/A':
- continue
- newtime = time.strptime(newresult[attr][0], '%Y%m%d%H%M%SZ')
- newresult[attr][0] = unicode(time.strftime('%Y-%m-%dT%H:%M:%SZ', newtime))
- except Exception as e:
- self.debug("time conversion failed with %s" % str(e))
- newresult['server'] = host
- if options.get('raw', False):
- time_format = '%Y%m%d%H%M%SZ'
- else:
- time_format = '%Y-%m-%dT%H:%M:%SZ'
- newresult['now'] = unicode(strftime(time_format, gmtime()))
- convert_nsaccountlock(entry)
- if 'nsaccountlock' in entry:
- disabled = entry['nsaccountlock']
- self.obj.get_preserved_attribute(entry, options)
- entries.append(newresult)
- count += 1
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- except Exception as e:
- self.error("user_status: Retrieving status for %s failed with %s" % (dn, str(e)))
- newresult = {'dn': dn}
- newresult['server'] = _("%(host)s failed") % dict(host=host)
- entries.append(newresult)
- count += 1
-
- if host != api.env.host:
- other_ldap.disconnect()
-
- return dict(result=entries,
- count=count,
- truncated=False,
- summary=unicode(_('Account disabled: %(disabled)s' %
- dict(disabled=disabled))),
- )
-
-
-@register()
-class user_add_cert(LDAPAddAttribute):
- __doc__ = _('Add one or more certificates to the user entry')
- msg_summary = _('Added certificates to user "%(value)s"')
- attribute = 'usercertificate'
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- dn = self.obj.get_either_dn(*keys, **options)
-
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- self.obj.convert_usercertificate_post(entry_attrs, **options)
-
- return dn
-
-
-@register()
-class user_remove_cert(LDAPRemoveAttribute):
- __doc__ = _('Remove one or more certificates to the user entry')
- msg_summary = _('Removed certificates from user "%(value)s"')
- attribute = 'usercertificate'
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- dn = self.obj.get_either_dn(*keys, **options)
-
- self.obj.convert_usercertificate_pre(entry_attrs)
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- assert isinstance(dn, DN)
-
- self.obj.convert_usercertificate_post(entry_attrs, **options)
-
- return dn
-
-
-@register()
-class user_add_manager(baseuser_add_manager):
- __doc__ = _("Add a manager to the user entry")
-
-
-@register()
-class user_remove_manager(baseuser_remove_manager):
- __doc__ = _("Remove a manager to the user entry")
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
deleted file mode 100644
index 05db63cdc..000000000
--- a/ipalib/plugins/vault.py
+++ /dev/null
@@ -1,1215 +0,0 @@
-# Authors:
-# Endi S. Dewata <edewata@redhat.com>
-#
-# Copyright (C) 2015 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 ipalib.frontend import Command, Object
-from ipalib import api, errors
-from ipalib import Bytes, Flag, Str, StrEnum
-from ipalib import output
-from ipalib.crud import PKQuery, Retrieve
-from ipalib.plugable import Registry
-from .baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
- LDAPSearch, LDAPUpdate, LDAPRetrieve, LDAPAddMember, LDAPRemoveMember,\
- LDAPModMember, pkey_to_value
-from ipalib.request import context
-from .baseuser import split_principal
-from .service import normalize_principal
-from ipalib import _, ngettext
-from ipapython.dn import DN
-
-if api.env.in_server:
- import pki.account
- import pki.key
-
-__doc__ = _("""
-Vaults
-""") + _("""
-Manage vaults.
-""") + _("""
-Vault is a secure place to store a secret.
-""") + _("""
-Based on the ownership there are three vault categories:
-* user/private vault
-* service vault
-* shared vault
-""") + _("""
-User vaults are vaults owned used by a particular user. Private
-vaults are vaults owned the current user. Service vaults are
-vaults owned by a service. Shared vaults are owned by the admin
-but they can be used by other users or services.
-""") + _("""
-Based on the security mechanism there are three types of
-vaults:
-* standard vault
-* symmetric vault
-* asymmetric vault
-""") + _("""
-Standard vault uses a secure mechanism to transport and
-store the secret. The secret can only be retrieved by users
-that have access to the vault.
-""") + _("""
-Symmetric vault is similar to the standard vault, but it
-pre-encrypts the secret using a password before transport.
-The secret can only be retrieved using the same password.
-""") + _("""
-Asymmetric vault is similar to the standard vault, but it
-pre-encrypts the secret using a public key before transport.
-The secret can only be retrieved using the private key.
-""") + _("""
-EXAMPLES:
-""") + _("""
- List vaults:
- ipa vault-find
- [--user <user>|--service <service>|--shared]
-""") + _("""
- Add a standard vault:
- ipa vault-add <name>
- [--user <user>|--service <service>|--shared]
- --type standard
-""") + _("""
- Add a symmetric vault:
- ipa vault-add <name>
- [--user <user>|--service <service>|--shared]
- --type symmetric --password-file password.txt
-""") + _("""
- Add an asymmetric vault:
- ipa vault-add <name>
- [--user <user>|--service <service>|--shared]
- --type asymmetric --public-key-file public.pem
-""") + _("""
- Show a vault:
- ipa vault-show <name>
- [--user <user>|--service <service>|--shared]
-""") + _("""
- Modify vault description:
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --desc <description>
-""") + _("""
- Modify vault type:
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --type <type>
- [old password/private key]
- [new password/public key]
-""") + _("""
- Modify symmetric vault password:
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --change-password
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --old-password <old password>
- --new-password <new password>
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --old-password-file <old password file>
- --new-password-file <new password file>
-""") + _("""
- Modify asymmetric vault keys:
- ipa vault-mod <name>
- [--user <user>|--service <service>|--shared]
- --private-key-file <old private key file>
- --public-key-file <new public key file>
-""") + _("""
- Delete a vault:
- ipa vault-del <name>
- [--user <user>|--service <service>|--shared]
-""") + _("""
- Display vault configuration:
- ipa vaultconfig-show
-""") + _("""
- Archive data into standard vault:
- ipa vault-archive <name>
- [--user <user>|--service <service>|--shared]
- --in <input file>
-""") + _("""
- Archive data into symmetric vault:
- ipa vault-archive <name>
- [--user <user>|--service <service>|--shared]
- --in <input file>
- --password-file password.txt
-""") + _("""
- Archive data into asymmetric vault:
- ipa vault-archive <name>
- [--user <user>|--service <service>|--shared]
- --in <input file>
-""") + _("""
- Retrieve data from standard vault:
- ipa vault-retrieve <name>
- [--user <user>|--service <service>|--shared]
- --out <output file>
-""") + _("""
- Retrieve data from symmetric vault:
- ipa vault-retrieve <name>
- [--user <user>|--service <service>|--shared]
- --out <output file>
- --password-file password.txt
-""") + _("""
- Retrieve data from asymmetric vault:
- ipa vault-retrieve <name>
- [--user <user>|--service <service>|--shared]
- --out <output file> --private-key-file private.pem
-""") + _("""
- Add vault owners:
- ipa vault-add-owner <name>
- [--user <user>|--service <service>|--shared]
- [--users <users>] [--groups <groups>] [--services <services>]
-""") + _("""
- Delete vault owners:
- ipa vault-remove-owner <name>
- [--user <user>|--service <service>|--shared]
- [--users <users>] [--groups <groups>] [--services <services>]
-""") + _("""
- Add vault members:
- ipa vault-add-member <name>
- [--user <user>|--service <service>|--shared]
- [--users <users>] [--groups <groups>] [--services <services>]
-""") + _("""
- Delete vault members:
- ipa vault-remove-member <name>
- [--user <user>|--service <service>|--shared]
- [--users <users>] [--groups <groups>] [--services <services>]
-""")
-
-
-register = Registry()
-
-vault_options = (
- Str(
- 'service?',
- doc=_('Service name of the service vault'),
- normalizer=normalize_principal,
- ),
- Flag(
- 'shared?',
- doc=_('Shared vault'),
- ),
- Str(
- 'username?',
- cli_name='user',
- doc=_('Username of the user vault'),
- ),
-)
-
-
-class VaultModMember(LDAPModMember):
- def get_options(self):
- for param in super(VaultModMember, self).get_options():
- if param.name == 'service' and param not in vault_options:
- param = param.clone_rename('services')
- yield param
-
- def get_member_dns(self, **options):
- if 'services' in options:
- options['service'] = options.pop('services')
- else:
- options.pop('service', None)
- return super(VaultModMember, self).get_member_dns(**options)
-
- def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
- for fail in failed.itervalues():
- fail['services'] = fail.pop('service', [])
- self.obj.get_container_attribute(entry_attrs, options)
- return completed, dn
-
-
-@register()
-class vaultcontainer(LDAPObject):
- __doc__ = _("""
- Vault Container object.
- """)
-
- container_dn = api.env.container_vault
-
- object_name = _('vaultcontainer')
- object_name_plural = _('vaultcontainers')
- object_class = ['ipaVaultContainer']
- permission_filter_objectclasses = ['ipaVaultContainer']
-
- attribute_members = {
- 'owner': ['user', 'group', 'service'],
- }
-
- label = _('Vault Containers')
- label_singular = _('Vault Container')
-
- managed_permissions = {
- 'System: Read Vault Containers': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'description', 'owner',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Add Vault Containers': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'add'},
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Delete Vault Containers': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'delete'},
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Modify Vault Containers': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'description',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Manage Vault Container Ownership': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'owner',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- }
-
- takes_params = (
- Str(
- 'owner_user?',
- label=_('Owner users'),
- ),
- Str(
- 'owner_group?',
- label=_('Owner groups'),
- ),
- Str(
- 'owner_service?',
- label=_('Owner services'),
- ),
- Str(
- 'owner?',
- label=_('Failed owners'),
- ),
- Str(
- 'service?',
- label=_('Vault service'),
- flags={'virtual_attribute'},
- ),
- Flag(
- 'shared?',
- label=_('Shared vault'),
- flags={'virtual_attribute'},
- ),
- Str(
- 'username?',
- label=_('Vault user'),
- flags={'virtual_attribute'},
- ),
- )
-
- def get_dn(self, *keys, **options):
- """
- Generates vault DN from parameters.
- """
- service = options.get('service')
- shared = options.get('shared')
- user = options.get('username')
-
- count = (bool(service) + bool(shared) + bool(user))
- if count > 1:
- raise errors.MutuallyExclusiveError(
- reason=_('Service, shared and user options ' +
- 'cannot be specified simultaneously'))
-
- parent_dn = super(vaultcontainer, self).get_dn(*keys, **options)
-
- if not count:
- principal = getattr(context, 'principal')
-
- if principal.startswith('host/'):
- raise errors.NotImplementedError(
- reason=_('Host is not supported'))
-
- (name, realm) = split_principal(principal)
- if '/' in name:
- service = principal
- else:
- user = name
-
- if service:
- dn = DN(('cn', service), ('cn', 'services'), parent_dn)
- elif shared:
- dn = DN(('cn', 'shared'), parent_dn)
- elif user:
- dn = DN(('cn', user), ('cn', 'users'), parent_dn)
- else:
- raise RuntimeError
-
- return dn
-
- def get_container_attribute(self, entry, options):
- if options.get('raw', False):
- return
- container_dn = DN(self.container_dn, self.api.env.basedn)
- if entry.dn.endswith(DN(('cn', 'services'), container_dn)):
- entry['service'] = entry.dn[0]['cn']
- elif entry.dn.endswith(DN(('cn', 'shared'), container_dn)):
- entry['shared'] = True
- elif entry.dn.endswith(DN(('cn', 'users'), container_dn)):
- entry['username'] = entry.dn[0]['cn']
-
-
-@register()
-class vaultcontainer_show(LDAPRetrieve):
- __doc__ = _('Display information about a vault container.')
-
- takes_options = LDAPRetrieve.takes_options + vault_options
-
- has_output_params = LDAPRetrieve.has_output_params
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.get_container_attribute(entry_attrs, options)
- return dn
-
-
-@register()
-class vaultcontainer_del(LDAPDelete):
- __doc__ = _('Delete a vault container.')
-
- takes_options = LDAPDelete.takes_options + vault_options
-
- msg_summary = _('Deleted vault container')
-
- subtree_delete = False
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- return dn
-
- def execute(self, *keys, **options):
- keys = keys + (u'',)
- return super(vaultcontainer_del, self).execute(*keys, **options)
-
-
-@register()
-class vaultcontainer_add_owner(VaultModMember, LDAPAddMember):
- __doc__ = _('Add owners to a vault container.')
-
- takes_options = LDAPAddMember.takes_options + vault_options
-
- member_attributes = ['owner']
- member_param_label = _('owner %s')
- member_count_out = ('%i owner added.', '%i owners added.')
-
- has_output = (
- output.Entry('result'),
- output.Output(
- 'failed',
- type=dict,
- doc=_('Owners that could not be added'),
- ),
- output.Output(
- 'completed',
- type=int,
- doc=_('Number of owners added'),
- ),
- )
-
-
-@register()
-class vaultcontainer_remove_owner(VaultModMember, LDAPRemoveMember):
- __doc__ = _('Remove owners from a vault container.')
-
- takes_options = LDAPRemoveMember.takes_options + vault_options
-
- member_attributes = ['owner']
- member_param_label = _('owner %s')
- member_count_out = ('%i owner removed.', '%i owners removed.')
-
- has_output = (
- output.Entry('result'),
- output.Output(
- 'failed',
- type=dict,
- doc=_('Owners that could not be removed'),
- ),
- output.Output(
- 'completed',
- type=int,
- doc=_('Number of owners removed'),
- ),
- )
-
-
-@register()
-class vault(LDAPObject):
- __doc__ = _("""
- Vault object.
- """)
-
- container_dn = api.env.container_vault
-
- object_name = _('vault')
- object_name_plural = _('vaults')
-
- object_class = ['ipaVault']
- permission_filter_objectclasses = ['ipaVault']
- default_attributes = [
- 'cn',
- 'description',
- 'ipavaulttype',
- 'ipavaultsalt',
- 'ipavaultpublickey',
- 'owner',
- 'member',
- ]
- search_display_attributes = [
- 'cn',
- 'description',
- 'ipavaulttype',
- ]
- attribute_members = {
- 'owner': ['user', 'group', 'service'],
- 'member': ['user', 'group', 'service'],
- }
-
- label = _('Vaults')
- label_singular = _('Vault')
-
- managed_permissions = {
- 'System: Read Vaults': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'read', 'search', 'compare'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'description', 'ipavaulttype',
- 'ipavaultsalt', 'ipavaultpublickey', 'owner', 'member',
- 'memberuser', 'memberhost',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Add Vaults': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'add'},
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Delete Vaults': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'delete'},
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Modify Vaults': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'objectclass', 'cn', 'description', 'ipavaulttype',
- 'ipavaultsalt', 'ipavaultpublickey',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Manage Vault Ownership': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'owner',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- 'System: Manage Vault Membership': {
- 'ipapermlocation': api.env.basedn,
- 'ipapermtarget': DN(api.env.container_vault, api.env.basedn),
- 'ipapermright': {'write'},
- 'ipapermdefaultattr': {
- 'member',
- },
- 'default_privileges': {'Vault Administrators'},
- },
- }
-
- takes_params = (
- Str(
- 'cn',
- cli_name='name',
- label=_('Vault name'),
- primary_key=True,
- pattern='^[a-zA-Z0-9_.-]+$',
- pattern_errmsg='may only include letters, numbers, _, ., and -',
- maxlength=255,
- ),
- Str(
- 'description?',
- cli_name='desc',
- label=_('Description'),
- doc=_('Vault description'),
- ),
- StrEnum(
- 'ipavaulttype?',
- cli_name='type',
- label=_('Type'),
- doc=_('Vault type'),
- values=(u'standard', u'symmetric', u'asymmetric', ),
- default=u'symmetric',
- autofill=True,
- ),
- Bytes(
- 'ipavaultsalt?',
- cli_name='salt',
- label=_('Salt'),
- doc=_('Vault salt'),
- flags=['no_search'],
- ),
- Bytes(
- 'ipavaultpublickey?',
- cli_name='public_key',
- label=_('Public key'),
- doc=_('Vault public key'),
- flags=['no_search'],
- ),
- Str(
- 'owner_user?',
- label=_('Owner users'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str(
- 'owner_group?',
- label=_('Owner groups'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str(
- 'owner_service?',
- label=_('Owner services'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str(
- 'owner?',
- label=_('Failed owners'),
- flags=['no_create', 'no_update', 'no_search'],
- ),
- Str(
- 'service?',
- label=_('Vault service'),
- flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
- ),
- Flag(
- 'shared?',
- label=_('Shared vault'),
- flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
- ),
- Str(
- 'username?',
- label=_('Vault user'),
- flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
- ),
- )
-
- def get_dn(self, *keys, **options):
- """
- Generates vault DN from parameters.
- """
- service = options.get('service')
- shared = options.get('shared')
- user = options.get('username')
-
- count = (bool(service) + bool(shared) + bool(user))
- if count > 1:
- raise errors.MutuallyExclusiveError(
- reason=_('Service, shared, and user options ' +
- 'cannot be specified simultaneously'))
-
- # TODO: create container_dn after object initialization then reuse it
- container_dn = DN(self.container_dn, self.api.env.basedn)
-
- dn = super(vault, self).get_dn(*keys, **options)
- assert dn.endswith(container_dn)
- rdns = DN(*dn[:-len(container_dn)])
-
- if not count:
- principal = getattr(context, 'principal')
-
- if principal.startswith('host/'):
- raise errors.NotImplementedError(
- reason=_('Host is not supported'))
-
- (name, realm) = split_principal(principal)
- if '/' in name:
- service = principal
- else:
- user = name
-
- if service:
- parent_dn = DN(('cn', service), ('cn', 'services'), container_dn)
- elif shared:
- parent_dn = DN(('cn', 'shared'), container_dn)
- elif user:
- parent_dn = DN(('cn', user), ('cn', 'users'), container_dn)
- else:
- raise RuntimeError
-
- return DN(rdns, parent_dn)
-
- def create_container(self, dn, owner_dn):
- """
- Creates vault container and its parents.
- """
-
- # TODO: create container_dn after object initialization then reuse it
- container_dn = DN(self.container_dn, self.api.env.basedn)
-
- entries = []
-
- while dn:
- assert dn.endswith(container_dn)
-
- rdn = dn[0]
- entry = self.backend.make_entry(
- dn,
- {
- 'objectclass': ['ipaVaultContainer'],
- 'cn': rdn['cn'],
- 'owner': [owner_dn],
- })
-
- # if entry can be added, return
- try:
- self.backend.add_entry(entry)
- break
-
- except errors.NotFound:
- pass
-
- # otherwise, create parent entry first
- dn = DN(*dn[1:])
- entries.insert(0, entry)
-
- # then create the entries again
- for entry in entries:
- self.backend.add_entry(entry)
-
- def get_key_id(self, dn):
- """
- Generates a client key ID to archive/retrieve data in KRA.
- """
-
- # TODO: create container_dn after object initialization then reuse it
- container_dn = DN(self.container_dn, self.api.env.basedn)
-
- # make sure the DN is a vault DN
- if not dn.endswith(container_dn, 1):
- raise ValueError('Invalid vault DN: %s' % dn)
-
- # construct the vault ID from the bottom up
- id = u''
- for rdn in dn[:-len(container_dn)]:
- name = rdn['cn']
- id = u'/' + name + id
-
- return 'ipa:' + id
-
- def get_container_attribute(self, entry, options):
- if options.get('raw', False):
- return
- container_dn = DN(self.container_dn, self.api.env.basedn)
- if entry.dn.endswith(DN(('cn', 'services'), container_dn)):
- entry['service'] = entry.dn[1]['cn']
- elif entry.dn.endswith(DN(('cn', 'shared'), container_dn)):
- entry['shared'] = True
- elif entry.dn.endswith(DN(('cn', 'users'), container_dn)):
- entry['username'] = entry.dn[1]['cn']
-
-
-@register()
-class vault_add_internal(LDAPCreate):
-
- NO_CLI = True
-
- takes_options = LDAPCreate.takes_options + vault_options
-
- msg_summary = _('Added vault "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
- **options):
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- principal = getattr(context, 'principal')
- (name, realm) = split_principal(principal)
- if '/' in name:
- owner_dn = self.api.Object.service.get_dn(name)
- else:
- owner_dn = self.api.Object.user.get_dn(name)
-
- parent_dn = DN(*dn[1:])
-
- try:
- self.obj.create_container(parent_dn, owner_dn)
- except errors.DuplicateEntry as e:
- pass
-
- # vault should be owned by the creator
- entry_attrs['owner'] = owner_dn
-
- return dn
-
- def post_callback(self, ldap, dn, entry, *keys, **options):
- self.obj.get_container_attribute(entry, options)
- return dn
-
-
-@register()
-class vault_del(LDAPDelete):
- __doc__ = _('Delete a vault.')
-
- takes_options = LDAPDelete.takes_options + vault_options
-
- msg_summary = _('Deleted vault "%(value)s"')
-
- def pre_callback(self, ldap, dn, *keys, **options):
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- return dn
-
- def post_callback(self, ldap, dn, *args, **options):
- assert isinstance(dn, DN)
-
- kra_client = self.api.Backend.kra.get_client()
-
- kra_account = pki.account.AccountClient(kra_client.connection)
- kra_account.login()
-
- client_key_id = self.obj.get_key_id(dn)
-
- # deactivate vault record in KRA
- response = kra_client.keys.list_keys(
- client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE)
-
- for key_info in response.key_infos:
- kra_client.keys.modify_key_status(
- key_info.get_key_id(),
- pki.key.KeyClient.KEY_STATUS_INACTIVE)
-
- kra_account.logout()
-
- return True
-
-
-@register()
-class vault_find(LDAPSearch):
- __doc__ = _('Search for vaults.')
-
- takes_options = LDAPSearch.takes_options + vault_options + (
- Flag(
- 'services?',
- doc=_('List all service vaults'),
- ),
- Flag(
- 'users?',
- doc=_('List all user vaults'),
- ),
- )
-
- has_output_params = LDAPSearch.has_output_params
-
- msg_summary = ngettext(
- '%(count)d vault matched',
- '%(count)d vaults matched',
- 0,
- )
-
- def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args,
- **options):
- assert isinstance(base_dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- if options.get('users') or options.get('services'):
- mutex = ['service', 'services', 'shared', 'username', 'users']
- count = sum(bool(options.get(option)) for option in mutex)
- if count > 1:
- raise errors.MutuallyExclusiveError(
- reason=_('Service(s), shared, and user(s) options ' +
- 'cannot be specified simultaneously'))
-
- scope = ldap.SCOPE_SUBTREE
- container_dn = DN(self.obj.container_dn,
- self.api.env.basedn)
-
- if options.get('services'):
- base_dn = DN(('cn', 'services'), container_dn)
- else:
- base_dn = DN(('cn', 'users'), container_dn)
- else:
- base_dn = self.obj.get_dn(None, **options)
-
- return filter, base_dn, scope
-
- def post_callback(self, ldap, entries, truncated, *args, **options):
- for entry in entries:
- self.obj.get_container_attribute(entry, options)
- return truncated
-
- def exc_callback(self, args, options, exc, call_func, *call_args,
- **call_kwargs):
- if call_func.__name__ == 'find_entries':
- if isinstance(exc, errors.NotFound):
- # ignore missing containers since they will be created
- # automatically on vault creation.
- raise errors.EmptyResult(reason=str(exc))
-
- raise exc
-
-
-@register()
-class vault_mod_internal(LDAPUpdate):
-
- NO_CLI = True
-
- takes_options = LDAPUpdate.takes_options + vault_options
-
- msg_summary = _('Modified vault "%(value)s"')
-
- def pre_callback(self, ldap, dn, entry_attrs, attrs_list,
- *keys, **options):
-
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.get_container_attribute(entry_attrs, options)
- return dn
-
-
-@register()
-class vault_show(LDAPRetrieve):
- __doc__ = _('Display information about a vault.')
-
- takes_options = LDAPRetrieve.takes_options + vault_options
-
- has_output_params = LDAPRetrieve.has_output_params
-
- def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
- assert isinstance(dn, DN)
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- return dn
-
- def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
- self.obj.get_container_attribute(entry_attrs, options)
- return dn
-
-
-@register()
-class vaultconfig(Object):
- __doc__ = _('Vault configuration')
-
- takes_params = (
- Bytes(
- 'transport_cert',
- label=_('Transport Certificate'),
- ),
- )
-
-
-@register()
-class vaultconfig_show(Retrieve):
- __doc__ = _('Show vault configuration.')
-
- takes_options = (
- Str(
- 'transport_out?',
- doc=_('Output file to store the transport certificate'),
- ),
- )
-
- def execute(self, *args, **options):
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- kra_client = self.api.Backend.kra.get_client()
- transport_cert = kra_client.system_certs.get_transport_cert()
- return {
- 'result': {
- 'transport_cert': transport_cert.binary
- },
- 'value': None,
- }
-
-
-@register()
-class vault_archive_internal(PKQuery):
-
- NO_CLI = True
-
- takes_options = vault_options + (
- Bytes(
- 'session_key',
- doc=_('Session key wrapped with transport certificate'),
- ),
- Bytes(
- 'vault_data',
- doc=_('Vault data encrypted with session key'),
- ),
- Bytes(
- 'nonce',
- doc=_('Nonce'),
- ),
- )
-
- has_output = output.standard_entry
-
- msg_summary = _('Archived data into vault "%(value)s"')
-
- def execute(self, *args, **options):
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- wrapped_vault_data = options.pop('vault_data')
- nonce = options.pop('nonce')
- wrapped_session_key = options.pop('session_key')
-
- # retrieve vault info
- vault = self.api.Command.vault_show(*args, **options)['result']
-
- # connect to KRA
- kra_client = self.api.Backend.kra.get_client()
-
- kra_account = pki.account.AccountClient(kra_client.connection)
- kra_account.login()
-
- client_key_id = self.obj.get_key_id(vault['dn'])
-
- # deactivate existing vault record in KRA
- response = kra_client.keys.list_keys(
- client_key_id,
- pki.key.KeyClient.KEY_STATUS_ACTIVE)
-
- for key_info in response.key_infos:
- kra_client.keys.modify_key_status(
- key_info.get_key_id(),
- pki.key.KeyClient.KEY_STATUS_INACTIVE)
-
- # forward wrapped data to KRA
- kra_client.keys.archive_encrypted_data(
- client_key_id,
- pki.key.KeyClient.PASS_PHRASE_TYPE,
- wrapped_vault_data,
- wrapped_session_key,
- None,
- nonce,
- )
-
- kra_account.logout()
-
- response = {
- 'value': args[-1],
- 'result': {},
- }
-
- response['summary'] = self.msg_summary % response
-
- return response
-
-
-@register()
-class vault_retrieve_internal(PKQuery):
-
- NO_CLI = True
-
- takes_options = vault_options + (
- Bytes(
- 'session_key',
- doc=_('Session key wrapped with transport certificate'),
- ),
- )
-
- has_output = output.standard_entry
-
- msg_summary = _('Retrieved data from vault "%(value)s"')
-
- def execute(self, *args, **options):
-
- if not self.api.Command.kra_is_enabled()['result']:
- raise errors.InvocationError(
- format=_('KRA service is not enabled'))
-
- wrapped_session_key = options.pop('session_key')
-
- # retrieve vault info
- vault = self.api.Command.vault_show(*args, **options)['result']
-
- # connect to KRA
- kra_client = self.api.Backend.kra.get_client()
-
- kra_account = pki.account.AccountClient(kra_client.connection)
- kra_account.login()
-
- client_key_id = self.obj.get_key_id(vault['dn'])
-
- # find vault record in KRA
- response = kra_client.keys.list_keys(
- client_key_id,
- pki.key.KeyClient.KEY_STATUS_ACTIVE)
-
- if not len(response.key_infos):
- raise errors.NotFound(reason=_('No archived data.'))
-
- key_info = response.key_infos[0]
-
- # retrieve encrypted data from KRA
- key = kra_client.keys.retrieve_key(
- key_info.get_key_id(),
- wrapped_session_key)
-
- kra_account.logout()
-
- response = {
- 'value': args[-1],
- 'result': {
- 'vault_data': key.encrypted_data,
- 'nonce': key.nonce_data,
- },
- }
-
- response['summary'] = self.msg_summary % response
-
- return response
-
-
-@register()
-class vault_add_owner(VaultModMember, LDAPAddMember):
- __doc__ = _('Add owners to a vault.')
-
- takes_options = LDAPAddMember.takes_options + vault_options
-
- member_attributes = ['owner']
- member_param_label = _('owner %s')
- member_count_out = ('%i owner added.', '%i owners added.')
-
- has_output = (
- output.Entry('result'),
- output.Output(
- 'failed',
- type=dict,
- doc=_('Owners that could not be added'),
- ),
- output.Output(
- 'completed',
- type=int,
- doc=_('Number of owners added'),
- ),
- )
-
-
-@register()
-class vault_remove_owner(VaultModMember, LDAPRemoveMember):
- __doc__ = _('Remove owners from a vault.')
-
- takes_options = LDAPRemoveMember.takes_options + vault_options
-
- member_attributes = ['owner']
- member_param_label = _('owner %s')
- member_count_out = ('%i owner removed.', '%i owners removed.')
-
- has_output = (
- output.Entry('result'),
- output.Output(
- 'failed',
- type=dict,
- doc=_('Owners that could not be removed'),
- ),
- output.Output(
- 'completed',
- type=int,
- doc=_('Number of owners removed'),
- ),
- )
-
-
-@register()
-class vault_add_member(VaultModMember, LDAPAddMember):
- __doc__ = _('Add members to a vault.')
-
- takes_options = LDAPAddMember.takes_options + vault_options
-
-
-@register()
-class vault_remove_member(VaultModMember, LDAPRemoveMember):
- __doc__ = _('Remove members from a vault.')
-
- takes_options = LDAPRemoveMember.takes_options + vault_options
-
-
-@register()
-class kra_is_enabled(Command):
- NO_CLI = True
-
- has_output = output.standard_value
-
- def execute(self, *args, **options):
- base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
- self.api.env.basedn)
- filter = '(&(objectClass=ipaConfigObject)(cn=KRA))'
- try:
- self.api.Backend.ldap2.find_entries(
- base_dn=base_dn, filter=filter, attrs_list=[])
- except errors.NotFound:
- result = False
- else:
- result = True
- return dict(result=result, value=pkey_to_value(None, options))
diff --git a/ipalib/plugins/virtual.py b/ipalib/plugins/virtual.py
deleted file mode 100644
index 2ba69f651..000000000
--- a/ipalib/plugins/virtual.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Authors:
-# Rob Crittenden <rcritten@redhat.com>
-#
-# Copyright (C) 2009 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, 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/>.
-
-"""
-Base classes for non-LDAP backend plugins.
-"""
-from ipalib import Command
-from ipalib import errors
-from ipapython.dn import DN
-from ipalib.text import _
-
-class VirtualCommand(Command):
- """
- A command that doesn't use the LDAP backend but wants to use the
- LDAP access control system to make authorization decisions.
-
- The class variable operation is the commonName attribute of the
- entry to be tested against.
-
- In advance, you need to create an entry of the form:
- cn=<operation>, api.env.container_virtual, api.env.basedn
-
- Ex.
- cn=request certificate, cn=virtual operations,cn=etc, dc=example, dc=com
- """
- operation = None
-
- def check_access(self, operation=None):
- """
- Perform an LDAP query to determine authorization.
-
- This should be executed before any actual work is done.
- """
- if self.operation is None and operation is None:
- raise errors.ACIError(info=_('operation not defined'))
-
- if operation is None:
- operation = self.operation
-
- ldap = self.api.Backend.ldap2
- self.log.debug("IPA: virtual verify %s" % operation)
-
- operationdn = DN(('cn', operation), self.api.env.container_virtual, self.api.env.basedn)
-
- try:
- if not ldap.can_write(operationdn, "objectclass"):
- raise errors.ACIError(
- info=_('not allowed to perform operation: %s') % operation)
- except errors.NotFound:
- raise errors.ACIError(info=_('No such virtual command'))
-
- return True
diff --git a/ipalib/setup.py.in b/ipalib/setup.py.in
index 8107c81ff..3bc6b7590 100644
--- a/ipalib/setup.py.in
+++ b/ipalib/setup.py.in
@@ -59,9 +59,7 @@ def setup_package():
classifiers=[line for line in CLASSIFIERS.split('\n') if line],
platforms = ["Linux", "Solaris", "Unix"],
package_dir = {'ipalib': ''},
- packages = ["ipalib",
- "ipalib.plugins",
- ],
+ packages = ["ipalib"],
)
finally:
del sys.path[0]