summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/aci.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2010-12-01 11:23:52 -0500
committerSimo Sorce <ssorce@redhat.com>2010-12-01 20:42:31 -0500
commit4ad8055341b9f12c833abdf757755ed95f1b375e (patch)
tree1733fffdfe47574f2c5eb723e52f88cd58a7e0a4 /ipalib/plugins/aci.py
parent85d5bfd1b19b0ed6282a8c6cc056e8e550dde79d (diff)
downloadfreeipa-4ad8055341b9f12c833abdf757755ed95f1b375e.tar.gz
freeipa-4ad8055341b9f12c833abdf757755ed95f1b375e.tar.xz
freeipa-4ad8055341b9f12c833abdf757755ed95f1b375e.zip
Re-implement access control using an updated model.
The new model is based on permssions, privileges and roles. Most importantly it corrects the reverse membership that caused problems in the previous implementation. You add permission to privileges and privileges to roles, not the other way around (even though it works that way behind the scenes). A permission object is a combination of a simple group and an aci. The linkage between the aci and the permission is the description of the permission. This shows as the name/description of the aci. ldap:///self and groups granting groups (v1-style) are not supported by this model (it will be provided separately). This makes the aci plugin internal only. ticket 445
Diffstat (limited to 'ipalib/plugins/aci.py')
-rw-r--r--ipalib/plugins/aci.py179
1 files changed, 143 insertions, 36 deletions
diff --git a/ipalib/plugins/aci.py b/ipalib/plugins/aci.py
index 153798924..c0f47e301 100644
--- a/ipalib/plugins/aci.py
+++ b/ipalib/plugins/aci.py
@@ -28,11 +28,11 @@ 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, rolegroups.
+roles are another type of group, roles.
For example, if you have taskgroups that allow adding and modifying users you
-could create a rolegroup, useradmin. You would assign users to the useradmin
-rolegroup to allow them to do the operations defined by the taskgroups.
+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.
@@ -64,7 +64,7 @@ be editabe.
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 rolegroups.
+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
@@ -80,6 +80,9 @@ EXAMPLES:
Add an ACI that allows members of the "addusers" taskgroup to add new users:
ipa aci-add --type=user --taskgroup=addusers --permissions=add "Add new users"
+ Add an ACI that lets members of the edotors manage members of the admins group:
+ ipa aci-add --permissions=write --attrs=member --targetgroup=admins --group=editors "Editors manage admins"
+
The show command shows the raw 389-ds ACI.
IMPORTANT: When modifying the target attributes of an existing ACI you
@@ -148,25 +151,25 @@ def _make_aci(current, aciname, kw):
raise errors.ValidationError(name='target', error=_('at least one of: type, filter, subtree, targetgroup, attrs or memberof are required'))
group = 'group' in kw
- taskgroup = 'taskgroup' in kw
+ permission = 'permission' in kw
selfaci = 'selfaci' in kw and kw['selfaci'] == True
- if group + taskgroup + selfaci > 1:
- raise errors.ValidationError(name='target', error=_('group, taskgroup and self are mutually exclusive'))
- elif group + taskgroup + selfaci == 0:
- raise errors.ValidationError(name='target', error=_('One of group, taskgroup or self is required'))
+ 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
- # taskgroup or a user group.
+ # permission or a user group.
entry_attrs = []
- if taskgroup:
+ if permission:
+ # This will raise NotFound if the permission doesn't exist
try:
- entry_attrs = api.Command['taskgroup_show'](kw['taskgroup'])['result']
- except errors.NotFound:
- # The task group doesn't exist, let's be helpful and add it
- tgkw = {'description': aciname}
- entry_attrs = api.Command['taskgroup_add'](
- kw['taskgroup'], **tgkw
- )['result']
+ entry_attrs = api.Command['permission_show'](kw['permission'])['result']
+ except errors.NotFound, e:
+ if 'test' in kw and not kw.get('test'):
+ raise e
+ else:
+ entry_attrs = {'dn': 'cn=%s,%s' % (kw['permission'], api.env.container_permission)}
elif group:
# Not so friendly with groups. This will raise
try:
@@ -186,7 +189,7 @@ def _make_aci(current, aciname, kw):
a.set_target_attr(kw['attrs'])
if 'memberof' in kw:
entry_attrs = api.Command['group_show'](kw['memberof'])['result']
- a.set_target_filter('memberOf=%s' % dn)
+ a.set_target_filter('memberOf=%s' % entry_attrs['dn'])
if 'filter' in kw:
a.set_target_filter(kw['filter'])
if 'type' in kw:
@@ -195,7 +198,7 @@ def _make_aci(current, aciname, kw):
if 'targetgroup' in kw:
# Purposely no try here so we'll raise a NotFound
entry_attrs = api.Command['group_show'](kw['targetgroup'])['result']
- target = 'ldap:///%s' % dn
+ target = 'ldap:///%s' % entry_attrs['dn']
a.set_target(target)
if 'subtree' in kw:
# See if the subtree is a full URI
@@ -206,7 +209,7 @@ def _make_aci(current, aciname, kw):
return a
-def _aci_to_kw(ldap, a):
+def _aci_to_kw(ldap, a, test=False):
"""Convert an ACI into its equivalent keywords.
This is used for the modify operation so we can merge the
@@ -254,11 +257,20 @@ def _aci_to_kw(ldap, a):
pass
else:
if groupdn.startswith('cn='):
- (dn, entry_attrs) = ldap.get_entry(groupdn, ['cn'])
- if api.env.container_taskgroup in dn:
- kw['taskgroup'] = entry_attrs['cn'][0]
+ dn = ''
+ entry_attrs = {}
+ try:
+ (dn, entry_attrs) = ldap.get_entry(groupdn, ['cn'])
+ except errors.NotFound, e:
+ # FIXME, use real name here
+ if test:
+ dn = 'cn=%s,%s' % ('test', api.env.container_permission)
+ entry_attrs = {'cn': [u'test']}
+ if api.env.container_permission in dn:
+ kw['permission'] = entry_attrs['cn'][0]
else:
- kw['group'] = entry_attrs['cn'][0]
+ if 'cn' in entry_attrs:
+ kw['group'] = entry_attrs['cn'][0]
return kw
@@ -299,6 +311,7 @@ class aci(Object):
"""
ACI object.
"""
+ INTERNAL = True
label = _('ACIs')
@@ -308,10 +321,10 @@ class aci(Object):
label=_('ACI name'),
primary_key=True,
),
- Str('taskgroup?',
- cli_name='taskgroup',
- label=_('Taskgroup'),
- doc=_('Taskgroup ACI grants access to'),
+ Str('permission?',
+ cli_name='permission',
+ label=_('Permission'),
+ doc=_('Permission ACI grants access to'),
),
Str('group?',
cli_name='group',
@@ -370,8 +383,16 @@ class aci_add(crud.Create):
"""
Create new ACI.
"""
+ INTERNAL = True
msg_summary = _('Created ACI "%(value)s"')
+ takes_options = (
+ Flag('test?',
+ doc=_('Test the ACI syntax but don\'t write anything'),
+ default=False,
+ ),
+ )
+
def execute(self, aciname, **kw):
"""
Execute the aci-create operation.
@@ -390,18 +411,20 @@ class aci_add(crud.Create):
acis = _convert_strings_to_acis(entry_attrs.get('aci', []))
for a in acis:
- if a.isequal(newaci):
+ # 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_attrs['aci'].append(newaci_str)
- ldap.update_entry(dn, entry_attrs)
+ if not kw.get('test', False):
+ ldap.update_entry(dn, entry_attrs)
if kw.get('raw', False):
result = dict(aci=unicode(newaci_str))
else:
- result = _aci_to_kw(ldap, newaci)
+ result = _aci_to_kw(ldap, newaci, kw.get('test', False))
return dict(
result=result,
value=aciname,
@@ -414,6 +437,7 @@ class aci_del(crud.Delete):
"""
Delete ACI.
"""
+ INTERNAL = True
has_output = output.standard_delete
msg_summary = _('Deleted ACI "%(value)s"')
@@ -454,6 +478,7 @@ class aci_mod(crud.Update):
"""
Modify ACI.
"""
+ INTERNAL = True
has_output_params = (
Str('aci',
label=_('ACI'),
@@ -485,6 +510,8 @@ class aci_mod(crud.Update):
# _make_aci is what is run in aci_add and validates the input.
# Do this before we delete the existing ACI.
newaci = _make_aci(None, aciname, newkw)
+ if aci.isequal(newaci):
+ raise errors.EmptyModlist()
self.api.Command['aci_del'](aciname)
@@ -522,6 +549,7 @@ class aci_find(crud.Search):
have ipausers as a memberof. There may be other ACIs that apply to
members of that group indirectly.
"""
+ INTERNAL = True
msg_summary = ngettext('%(count)d ACI matched', '%(count)d ACIs matched', 0)
def execute(self, term, **kw):
@@ -560,10 +588,10 @@ class aci_find(crud.Search):
results.remove(a)
acis = list(results)
- if 'taskgroup' in kw:
+ if 'permission' in kw:
try:
- self.api.Command['taskgroup_show'](
- kw['taskgroup']
+ self.api.Command['permission_show'](
+ kw['permission']
)
except errors.NotFound:
pass
@@ -600,7 +628,24 @@ class aci_find(crud.Search):
# uncomment next line if you add more search criteria
# acis = list(results)
- # TODO: searching by: type, filter, subtree
+ 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 'type' in kw and kw['type'] == k:
+ found = True
+ break;
+ if not found:
+ try:
+ results.remove(a)
+ except ValueError:
+ pass
+
+ # TODO: searching by: filter, subtree
acis = []
for result in results:
@@ -623,6 +668,7 @@ class aci_show(crud.Retrieve):
"""
Display a single ACI given an ACI name.
"""
+ INTERNAL = True
has_output_params = (
Str('aci',
@@ -656,3 +702,64 @@ class aci_show(crud.Retrieve):
)
api.register(aci_show)
+
+
+class aci_rename(crud.Update):
+ """
+ Rename an ACI.
+ """
+ INTERNAL = True
+ has_output_params = (
+ Str('aci',
+ label=_('ACI'),
+ ),
+ )
+
+ takes_options = (
+ Str('newname',
+ doc=_('New ACI name'),
+ ),
+ )
+
+ msg_summary = _('Renameed ACI to "%(value)s"')
+
+ def execute(self, aciname, **kw):
+ ldap = self.api.Backend.ldap2
+
+ (dn, entry_attrs) = ldap.get_entry(self.api.env.basedn, ['aci'])
+
+ acis = _convert_strings_to_acis(entry_attrs.get('aci', []))
+ aci = _find_aci_by_name(acis, aciname)
+
+ for a in acis:
+ if 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(None, kw['newname'], newkw)
+
+ self.api.Command['aci_del'](aciname)
+
+ 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=kw['newname'],
+ )
+
+api.register(aci_rename)