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>
This commit is contained in:
Petr Viktorin 2013-09-13 16:08:22 +02:00
parent eb14f99ece
commit 3db08227e8
7 changed files with 780 additions and 88 deletions

14
API.txt
View File

@ -2322,13 +2322,12 @@ output: Output('result', <type 'bool'>, None)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('value', <type 'unicode'>, None)
command: permission_add
args: 1,19,3
args: 1,18,3
arg: Str('cn', attribute=True, cli_name='name', multivalue=False, pattern='^[-_ a-zA-Z0-9.]+$', primary_key=True, required=True)
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('attrs', attribute=False, cli_name='attrs', multivalue=True, required=False)
option: Str('filter', attribute=False, cli_name='filter', multivalue=True, required=False)
option: Str('ipapermallowedattr', attribute=True, cli_name='attrs', multivalue=True, required=False)
option: StrEnum('ipapermbindruletype', attribute=True, autofill=True, cli_name='bindtype', default=u'permission', multivalue=False, required=True, values=(u'permission', u'all', u'anonymous'))
option: DNOrURL('ipapermlocation', alwaysask=True, attribute=True, autofill=False, cli_name='subtree', multivalue=False, query=False, required=False)
option: StrEnum('ipapermright', attribute=True, cli_name='permissions', multivalue=True, required=False, values=(u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'))
@ -2378,14 +2377,16 @@ output: Output('result', <type 'dict'>, None)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('value', <type 'unicode'>, None)
command: permission_find
args: 1,21,4
args: 1,23,4
arg: Str('criteria?', noextrawhitespace=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('attrs', attribute=False, autofill=False, cli_name='attrs', multivalue=True, query=True, required=False)
option: Str('cn', attribute=True, autofill=False, cli_name='name', multivalue=False, pattern='^[-_ a-zA-Z0-9.]+$', primary_key=True, query=True, required=False)
option: Str('filter', attribute=False, autofill=False, cli_name='filter', multivalue=True, query=True, required=False)
option: Str('ipapermallowedattr', attribute=True, autofill=False, cli_name='attrs', multivalue=True, query=True, required=False)
option: StrEnum('ipapermbindruletype', attribute=True, autofill=False, cli_name='bindtype', default=u'permission', multivalue=False, query=True, required=False, values=(u'permission', u'all', u'anonymous'))
option: Str('ipapermdefaultattr', attribute=True, autofill=False, cli_name='defaultattrs', multivalue=True, query=True, required=False)
option: Str('ipapermexcludedattr', attribute=True, autofill=False, cli_name='excludedattrs', multivalue=True, query=True, required=False)
option: Str('ipapermincludedattr', attribute=True, autofill=False, cli_name='includedattrs', multivalue=True, query=True, required=False)
option: DNOrURL('ipapermlocation', attribute=True, autofill=False, cli_name='subtree', multivalue=False, query=True, required=False)
option: StrEnum('ipapermright', attribute=True, autofill=False, cli_name='permissions', multivalue=True, query=True, required=False, values=(u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'))
option: DNParam('ipapermtarget', attribute=True, autofill=False, cli_name='target', multivalue=False, query=True, required=False)
@ -2406,15 +2407,16 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('truncated', <type 'bool'>, None)
command: permission_mod
args: 1,22,3
args: 1,23,3
arg: Str('cn', attribute=True, cli_name='name', multivalue=False, pattern='^[-_ a-zA-Z0-9.]+$', primary_key=True, query=True, required=True)
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('attrs', attribute=False, autofill=False, cli_name='attrs', multivalue=True, required=False)
option: Str('delattr*', cli_name='delattr', exclude='webui')
option: Str('filter', attribute=False, autofill=False, cli_name='filter', multivalue=True, required=False)
option: Str('ipapermallowedattr', attribute=True, autofill=False, cli_name='attrs', multivalue=True, required=False)
option: StrEnum('ipapermbindruletype', attribute=True, autofill=False, cli_name='bindtype', default=u'permission', multivalue=False, required=False, values=(u'permission', u'all', u'anonymous'))
option: Str('ipapermexcludedattr', attribute=True, autofill=False, cli_name='excludedattrs', multivalue=True, required=False)
option: Str('ipapermincludedattr', attribute=True, autofill=False, cli_name='includedattrs', multivalue=True, required=False)
option: DNOrURL('ipapermlocation', attribute=True, autofill=False, cli_name='subtree', multivalue=False, required=False)
option: StrEnum('ipapermright', attribute=True, autofill=False, cli_name='permissions', multivalue=True, required=False, values=(u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'))
option: DNParam('ipapermtarget', attribute=True, autofill=False, cli_name='target', multivalue=False, required=False)

View File

@ -89,4 +89,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=72
IPA_API_VERSION_MINOR=73
# Last change: pviktori - Managed permissions

View File

@ -39,7 +39,7 @@ attributeTypes: (2.16.840.1.113730.3.8.11.39 NAME 'ipaNTSIDBlacklistOutgoing' DE
attributeTypes: (2.16.840.1.113730.3.8.11.40 NAME 'ipaUserAuthType' DESC 'Allowed authentication methods' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3')
attributeTypes: (2.16.840.1.113730.3.8.11.41 NAME 'ipaRangeType' DESC 'Range type' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.42 NAME 'ipaPermDefaultAttr' DESC 'IPA permission default attribute' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.43 NAME 'ipaPermAllowedAttr' DESC 'IPA permission explicitly allowed attribute' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.43 NAME 'ipaPermIncludedAttr' DESC 'IPA permission explicitly included attribute' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.44 NAME 'ipaPermExcludedAttr' DESC 'IPA permission explicitly excluded attribute' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.45 NAME 'ipaPermBindRuleType' DESC 'IPA permission bind rule type' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v3' )
attributeTypes: (2.16.840.1.113730.3.8.11.46 NAME 'ipaPermLocation' DESC 'Location of IPA permission ACI' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v3' )
@ -63,4 +63,4 @@ objectClasses: (2.16.840.1.113730.3.8.12.16 NAME 'ipaDomainIDRange' SUP ipaIDran
objectClasses: (2.16.840.1.113730.3.8.12.17 NAME 'ipaTrustedADDomainRange' SUP ipaIDrange STRUCTURAL MUST ( ipaBaseRID $ ipaNTTrustedDomainSID ) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.19 NAME 'ipaUserAuthTypeClass' SUP top AUXILIARY DESC 'Class for authentication methods definition' MAY ipaUserAuthType X-ORIGIN 'IPA v3')
objectClasses: (2.16.840.1.113730.3.8.12.20 NAME 'ipaUser' AUXILIARY MUST ( uid ) MAY ( userClass ) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.21 NAME 'ipaPermissionV2' DESC 'IPA Permission objectclass, version 2' SUP ipaPermission AUXILIARY MUST ( ipaPermBindRuleType $ ipaPermLocation ) MAY ( ipaPermDefaultAttr $ ipaPermAllowedAttr $ ipaPermExcludedAttr $ ipaPermRight $ ipaPermTargetFilter $ ipaPermTarget ) X-ORIGIN 'IPA v3' )
objectClasses: (2.16.840.1.113730.3.8.12.21 NAME 'ipaPermissionV2' DESC 'IPA Permission objectclass, version 2' SUP ipaPermission AUXILIARY MUST ( ipaPermBindRuleType $ ipaPermLocation ) MAY ( ipaPermDefaultAttr $ ipaPermIncludedAttr $ ipaPermExcludedAttr $ ipaPermRight $ ipaPermTargetFilter $ ipaPermTarget ) X-ORIGIN 'IPA v3' )

View File

@ -353,6 +353,8 @@ class Param(ReadOnly):
can be unspecified (unchanged) but cannot be deleted.
* optional_create: do not require the parameter for crud.Create
based commands
* allow_mod_for_managed_permission: permission-mod allows changing
the parameter for managed permissions
- hint: this attribute is currently not used
- alwaysask: when enabled, CLI asks for parameter value even when the
parameter is not `required`

View File

@ -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)]

View File

@ -67,7 +67,8 @@ permission3_attributelevelrights = {
'type': u'rscwo',
'nsaccountlock': u'rscwo',
'description': u'rscwo',
'ipapermallowedattr': u'rscwo',
'attrs': u'rscwo',
'ipapermincludedattr': u'rscwo',
'ipapermbindruletype': u'rscwo',
'ipapermdefaultattr': u'rscwo',
'ipapermexcludedattr': u'rscwo',
@ -1093,6 +1094,7 @@ class test_old_permission(Declarative):
objectclass=objectclasses.permission,
type=u'user',
attrs=(u'cn',),
ipapermincludedattr=[u'cn'],
permissions=[u'write'],
attributelevelrights=permission3_attributelevelrights,
ipapermbindruletype=[u'permission'],
@ -1115,6 +1117,7 @@ class test_old_permission(Declarative):
objectclass=objectclasses.permission,
type=u'user',
attrs=(u'cn',u'uid'),
ipapermincludedattr=[u'cn', u'uid'],
permissions=[u'write'],
attributelevelrights=permission3_attributelevelrights,
ipapermbindruletype=[u'permission'],

File diff suppressed because it is too large Load Diff