summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/permission.py
diff options
context:
space:
mode:
authorPetr Viktorin <pviktori@redhat.com>2013-09-13 16:08:22 +0200
committerPetr Viktorin <pviktori@redhat.com>2014-02-12 17:11:17 +0100
commit3db08227e8c760c688b8886e0b3b072e9b6dd94d (patch)
tree225e3ea4f648e17500488ef5fea709554995a2a1 /ipalib/plugins/permission.py
parenteb14f99ece71170758399c16bee5b07a866f3775 (diff)
downloadfreeipa-3db08227e8c760c688b8886e0b3b072e9b6dd94d.tar.gz
freeipa-3db08227e8c760c688b8886e0b3b072e9b6dd94d.tar.xz
freeipa-3db08227e8c760c688b8886e0b3b072e9b6dd94d.zip
Add support for managed permissions
This adds support for managed permissions. The attribute list of these is computed from the "default" (modifiable only internally), "allowed", and "excluded" lists. This makes it possible to cleanly merge updated IPA defaults and user changes on upgrades. The default managed permissions are to be added in a future patch. For now they can only be created manually (see test_managed_permissions). Tests included. Part of the work for: https://fedorahosted.org/freeipa/ticket/4033 Design: http://www.freeipa.org/page/V3/Managed_Read_permissions Reviewed-By: Martin Kosek <mkosek@redhat.com>
Diffstat (limited to 'ipalib/plugins/permission.py')
-rw-r--r--ipalib/plugins/permission.py144
1 files changed, 131 insertions, 13 deletions
diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py
index 875a9f5b1..d003bcabb 100644
--- a/ipalib/plugins/permission.py
+++ b/ipalib/plugins/permission.py
@@ -79,6 +79,15 @@ Setting one of these options will set the corresponding attribute(s).
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:
@@ -95,12 +104,11 @@ VALID_OBJECT_TYPES = (u'user', u'group', u'host', u'service', u'hostgroup',
_DEPRECATED_OPTION_ALIASES = {
'permissions': 'ipapermright',
- 'attrs': 'ipapermallowedattr',
'filter': 'ipapermtargetfilter',
'subtree': 'ipapermlocation',
}
-KNOWN_FLAGS = {'SYSTEM', 'V2'}
+KNOWN_FLAGS = {'SYSTEM', 'V2', 'MANAGED'}
output_params = (
Str('aci',
@@ -139,7 +147,7 @@ class permission(baseldap.LDAPObject):
object_class = ['groupofnames', 'ipapermission', 'ipapermissionv2']
default_attributes = ['cn', 'member', 'memberof',
'memberindirect', 'ipapermissiontype', 'objectclass',
- 'ipapermdefaultattr', 'ipapermallowedattr', 'ipapermexcludedattr',
+ 'ipapermdefaultattr', 'ipapermincludedattr', 'ipapermexcludedattr',
'ipapermbindruletype', 'ipapermlocation', 'ipapermright',
'ipapermtargetfilter', 'ipapermtarget'
]
@@ -169,10 +177,29 @@ class permission(baseldap.LDAPObject):
values=(u'read', u'search', u'compare',
u'write', u'add', u'delete', u'all'),
),
- Str('ipapermallowedattr*',
- cli_name='attrs',
- label=_('Attributes'),
- doc=_('Attributes to which the permission applies'),
+ 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',
@@ -182,6 +209,7 @@ class permission(baseldap.LDAPObject):
autofill=True,
values=(u'permission', u'all', u'anonymous'),
default=u'permission',
+ flags={'allow_mod_for_managed_permission'},
),
DNOrURL(
'ipapermlocation?',
@@ -301,6 +329,12 @@ class permission(baseldap.LDAPObject):
rights['type'] = ''.join(sorted(type_rights,
key=rights['ipapermtarget'].index))
+ if 'ipapermincludedattr' in rights:
+ rights['attrs'] = ''.join(sorted(
+ set(rights['ipapermincludedattr']) &
+ set(rights.get('ipapermexcludedattr', '')),
+ key=rights['ipapermincludedattr'].index))
+
if not client_has_capability(options['version'], 'permissions2'):
for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items():
if new_name in entry:
@@ -319,6 +353,14 @@ class permission(baseldap.LDAPObject):
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 not client_has_capability(options['version'], 'permissions2'):
# Legacy clients expect some attributes as a single value
@@ -337,6 +379,12 @@ class permission(baseldap.LDAPObject):
new_filter.append(flt[1:-1])
entry['filter'] = new_filter
+ def get_effective_attrs(self, entry):
+ attrs = set(entry.get('ipapermdefaultattr', ()))
+ attrs.update(entry.get('ipapermincludedattr', ()))
+ attrs.difference_update(entry.get('ipapermexcludedattr', ()))
+ return sorted(attrs)
+
def make_aci(self, entry):
"""Make an ACI string from the given permission entry"""
@@ -344,7 +392,7 @@ class permission(baseldap.LDAPObject):
name = entry.single_value['cn']
# targetattr
- attrs = entry.get('ipapermallowedattr', [])
+ attrs = self.get_effective_attrs(entry)
if attrs:
aci_parts.append("(targetattr = \"%s\")" % ' || '.join(attrs))
@@ -502,9 +550,6 @@ class permission(baseldap.LDAPObject):
aci = ACI(acistring)
- if 'targetattr' in aci.target:
- target_entry['ipapermallowedattr'] = (
- aci.target['targetattr']['expression'])
if 'target' in aci.target:
target_entry.single_value['ipapermtarget'] = DN(strip_ldap_prefix(
aci.target['target']['expression']))
@@ -519,7 +564,7 @@ class permission(baseldap.LDAPObject):
target_entry.single_value['ipapermbindruletype'] = u'permission'
target_entry['ipapermright'] = aci.permissions
if 'targetattr' in aci.target:
- target_entry['ipapermallowedattr'] = [
+ target_entry['ipapermincludedattr'] = [
unicode(a) for a in aci.target['targetattr']['expression']]
if not output_only:
@@ -655,7 +700,8 @@ class permission(baseldap.LDAPObject):
# Ensure there's something in the ACI's filter
needed_attrs = (
- 'ipapermtarget', 'ipapermtargetfilter', 'ipapermallowedattr')
+ 'ipapermtarget', 'ipapermtargetfilter',
+ 'ipapermincludedattr', 'ipapermexcludedattr', 'ipapermdefaultattr')
if not any(entry.single_value.get(a) for a in needed_attrs):
raise errors.ValidationError(
name='target',
@@ -717,6 +763,14 @@ class permission_add(baseldap.LDAPCreate):
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
@@ -748,6 +802,9 @@ class permission_del(baseldap.LDAPDelete):
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)
@@ -783,6 +840,38 @@ class permission_mod(baseldap.LDAPUpdate):
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:
+ raise errors.ValidationError(
+ name=option_name,
+ 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(
@@ -866,7 +955,36 @@ class permission_find(baseldap.LDAPSearch):
self.obj.preprocess_options(options)
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)]