URL: https://github.com/freeipa/freeipa/pull/617
Author: stlaz
 Title: #617: Allow renaming of sudo and HBAC rules
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/617/head:pr617
git checkout pr617
From 1de05a1ae8507aca2fb43a0bccc34a566b7334d7 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Thu, 16 Mar 2017 16:22:52 +0100
Subject: [PATCH] Allow renaming of HBAC and sudo rules

This allows renaming of some objects that don't necessarily
contain their own private key in their DN.

TODO: control the check whether DN changed

https://pagure.io/freeipa/issue/2466
---
 API.txt                                |  6 ++++--
 VERSION.m4                             |  4 ++--
 ipaserver/plugins/automount.py         |  2 +-
 ipaserver/plugins/baseldap.py          | 32 ++++++++++++++++++++------------
 ipaserver/plugins/baseuser.py          |  2 +-
 ipaserver/plugins/ca.py                |  2 +-
 ipaserver/plugins/dns.py               |  2 +-
 ipaserver/plugins/group.py             |  2 +-
 ipaserver/plugins/hbacrule.py          |  1 +
 ipaserver/plugins/idviews.py           |  6 +++---
 ipaserver/plugins/otptoken.py          |  2 +-
 ipaserver/plugins/permission.py        |  2 +-
 ipaserver/plugins/privilege.py         |  2 +-
 ipaserver/plugins/radiusproxy.py       |  2 +-
 ipaserver/plugins/role.py              |  2 +-
 ipaserver/plugins/servicedelegation.py |  2 +-
 ipaserver/plugins/sudorule.py          |  1 +
 17 files changed, 42 insertions(+), 30 deletions(-)

diff --git a/API.txt b/API.txt
index f0bd1b6..7594157 100644
--- a/API.txt
+++ b/API.txt
@@ -2163,7 +2163,7 @@ output: ListOfEntries('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: Output('truncated', type=[<type 'bool'>])
 command: hbacrule_mod/1
-args: 1,16,3
+args: 1,17,3
 arg: Str('cn', cli_name='name')
 option: StrEnum('accessruletype?', autofill=False, cli_name='type', default=u'allow', values=[u'allow', u'deny'])
 option: Str('addattr*', cli_name='addattr')
@@ -2175,6 +2175,7 @@ option: StrEnum('hostcategory?', autofill=False, cli_name='hostcat', values=[u'a
 option: Bool('ipaenabledflag?', autofill=False)
 option: Flag('no_members', autofill=True, default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('rename?', cli_name='rename')
 option: Flag('rights', autofill=True, default=False)
 option: StrEnum('servicecategory?', autofill=False, cli_name='servicecat', values=[u'all'])
 option: Str('setattr*', cli_name='setattr')
@@ -5402,7 +5403,7 @@ output: ListOfEntries('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: Output('truncated', type=[<type 'bool'>])
 command: sudorule_mod/1
-args: 1,20,3
+args: 1,21,3
 arg: Str('cn', cli_name='sudorule_name')
 option: Str('addattr*', cli_name='addattr')
 option: Flag('all', autofill=True, cli_name='all', default=False)
@@ -5419,6 +5420,7 @@ option: StrEnum('ipasudorunasgroupcategory?', autofill=False, cli_name='runasgro
 option: StrEnum('ipasudorunasusercategory?', autofill=False, cli_name='runasusercat', values=[u'all'])
 option: Flag('no_members', autofill=True, default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('rename?', cli_name='rename')
 option: Flag('rights', autofill=True, default=False)
 option: Str('setattr*', cli_name='setattr')
 option: Int('sudoorder?', autofill=False, cli_name='order', default=0)
diff --git a/VERSION.m4 b/VERSION.m4
index cfac2a9..6c4213d 100644
--- a/VERSION.m4
+++ b/VERSION.m4
@@ -73,8 +73,8 @@ define(IPA_DATA_VERSION, 20100614120000)
 #                                                      #
 ########################################################
 define(IPA_API_VERSION_MAJOR, 2)
-define(IPA_API_VERSION_MINOR, 223)
-# Last change: Add domain resolution order to ID views
+define(IPA_API_VERSION_MINOR, 224)
+# Last change: Add rename option to some *_mod commands
 
 
 ########################################################
diff --git a/ipaserver/plugins/automount.py b/ipaserver/plugins/automount.py
index c4cf2d6..03f994c 100644
--- a/ipaserver/plugins/automount.py
+++ b/ipaserver/plugins/automount.py
@@ -456,7 +456,7 @@ class automountkey(LDAPObject):
     default_attributes = [
         'automountkey', 'automountinformation', 'description'
     ]
-    rdn_is_primary_key = True
+    allow_rename = True
     rdn_separator = ' '
 
     takes_params = (
diff --git a/ipaserver/plugins/baseldap.py b/ipaserver/plugins/baseldap.py
index 79ba7fc..dbe3cbd 100644
--- a/ipaserver/plugins/baseldap.py
+++ b/ipaserver/plugins/baseldap.py
@@ -36,7 +36,7 @@
 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.dn import DN, RDN
 from ipapython.version import API_VERSION
 
 if six.PY3:
@@ -549,7 +549,7 @@ class LDAPObject(Object):
     rdn_attribute = ''
     uuid_attribute = ''
     attribute_members = {}
-    rdn_is_primary_key = False # Do we need RDN change to do a rename?
+    allow_rename = False
     password_attributes = []
     # Can bind as this entry (has userPassword or krbPrincipalKey)
     bindable = False
@@ -1384,7 +1384,7 @@ def _get_rename_option(self):
     def get_options(self):
         for option in super(LDAPUpdate, self).get_options():
             yield option
-        if self.obj.rdn_is_primary_key:
+        if self.obj.allow_rename:
             yield self._get_rename_option()
 
     def execute(self, *keys, **options):
@@ -1419,15 +1419,19 @@ def execute(self, *keys, **options):
         _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:
+        if '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 setattr was used to change the RDN, the primary_key.name is
+        # already in entry_attrs
+        if self.obj.allow_rename and self.obj.primary_key.name in entry_attrs:
+            # perform RDN change if the primary key is also RDN
+            if (RDN((self.obj.primary_key.name, keys[-1])) ==
+                    entry_attrs.dn[0]):
                 try:
-                    # RDN change
                     new_dn = DN((self.obj.primary_key.name,
                                  entry_attrs[self.obj.primary_key.name]),
                                 *entry_attrs.dn[1:])
@@ -1435,17 +1439,21 @@ def execute(self, *keys, **options):
                         entry_attrs.dn,
                         new_dn)
 
-                    rdnkeys = keys[:-1] + (entry_attrs[self.obj.primary_key.name], )
+                    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
+                except errors.NotFound:
+                    self.obj.handle_not_found(*keys)
                 finally:
                     # Delete the primary_key from entry_attrs either way
                     del entry_attrs[self.obj.primary_key.name]
 
+        try:
             # 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
diff --git a/ipaserver/plugins/baseuser.py b/ipaserver/plugins/baseuser.py
index 44adc76..bf24dbf 100644
--- a/ipaserver/plugins/baseuser.py
+++ b/ipaserver/plugins/baseuser.py
@@ -164,7 +164,7 @@ class baseuser(LDAPObject):
         'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
         'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
     }
-    rdn_is_primary_key = True
+    allow_rename = True
     bindable = True
     password_attributes = [('userpassword', 'has_password'),
                            ('krbprincipalkey', 'has_keytab')]
diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py
index f774f78..9bb163d 100644
--- a/ipaserver/plugins/ca.py
+++ b/ipaserver/plugins/ca.py
@@ -68,7 +68,7 @@ class ca(LDAPObject):
         'cn', 'description', 'ipacaid', 'ipacaissuerdn', 'ipacasubjectdn',
     ]
     rdn_attribute = 'cn'
-    rdn_is_primary_key = True
+    allow_rename = True
     label = _('Certificate Authorities')
     label_singular = _('Certificate Authority')
 
diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py
index 7007928..47ac963 100644
--- a/ipaserver/plugins/dns.py
+++ b/ipaserver/plugins/dns.py
@@ -3000,7 +3000,7 @@ class dnsrecord(LDAPObject):
     possible_objectclasses = ['idnsTemplateObject']
     permission_filter_objectclasses = ['idnsrecord']
     default_attributes = ['idnsname'] + _record_attributes
-    rdn_is_primary_key = True
+    allow_rename = True
 
     label = _('DNS Resource Records')
     label_singular = _('DNS Resource Record')
diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py
index 218da3c..1fb092d 100644
--- a/ipaserver/plugins/group.py
+++ b/ipaserver/plugins/group.py
@@ -173,7 +173,7 @@ class group(LDAPObject):
         'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule',
         'sudorule'],
     }
-    rdn_is_primary_key = True
+    allow_rename = True
     managed_permissions = {
         'System: Read Groups': {
             'replaces_global_anonymous_aci': True,
diff --git a/ipaserver/plugins/hbacrule.py b/ipaserver/plugins/hbacrule.py
index 60e5e60..2495702 100644
--- a/ipaserver/plugins/hbacrule.py
+++ b/ipaserver/plugins/hbacrule.py
@@ -141,6 +141,7 @@ class hbacrule(LDAPObject):
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
+    allow_rename = True
     attribute_members = {
         'memberuser': ['user', 'group'],
         'memberhost': ['host', 'hostgroup'],
diff --git a/ipaserver/plugins/idviews.py b/ipaserver/plugins/idviews.py
index 6d4ac75..b5ee32c 100644
--- a/ipaserver/plugins/idviews.py
+++ b/ipaserver/plugins/idviews.py
@@ -97,7 +97,7 @@ class idview(LDAPObject):
     object_class = ['ipaIDView', 'top']
     possible_objectclasses = ['ipaNameResolutionData']
     default_attributes = ['cn', 'description', 'ipadomainresolutionorder']
-    rdn_is_primary_key = True
+    allow_rename = True
 
     label = _('ID Views')
     label_singular = _('ID View')
@@ -848,7 +848,7 @@ class idoverrideuser(baseidoverride):
 
     label = _('User ID overrides')
     label_singular = _('User ID override')
-    rdn_is_primary_key = True
+    allow_rename = True
 
     # ID user overrides are bindable because we map SASL GSSAPI
     # authentication of trusted users to ID user overrides in the
@@ -964,7 +964,7 @@ class idoverridegroup(baseidoverride):
 
     label = _('Group ID overrides')
     label_singular = _('Group ID override')
-    rdn_is_primary_key = True
+    allow_rename = True
 
     permission_filter_objectclasses = ['ipaGroupOverride']
     managed_permissions = {
diff --git a/ipaserver/plugins/otptoken.py b/ipaserver/plugins/otptoken.py
index 98ecbe5..c66f098 100644
--- a/ipaserver/plugins/otptoken.py
+++ b/ipaserver/plugins/otptoken.py
@@ -143,7 +143,7 @@ class otptoken(LDAPObject):
     relationships = {
         'managedby': ('Managed by', 'man_by_', 'not_man_by_'),
     }
-    rdn_is_primary_key = True
+    allow_rename = True
 
     label = _('OTP Tokens')
     label_singular = _('OTP Token')
diff --git a/ipaserver/plugins/permission.py b/ipaserver/plugins/permission.py
index dd2a018..977c6fe 100644
--- a/ipaserver/plugins/permission.py
+++ b/ipaserver/plugins/permission.py
@@ -188,7 +188,7 @@ class permission(baseldap.LDAPObject):
         'member': ['privilege'],
         'memberindirect': ['role'],
     }
-    rdn_is_primary_key = True
+    allow_rename = True
     managed_permissions = {
         'System: Read Permissions': {
             'replaces_global_anonymous_aci': True,
diff --git a/ipaserver/plugins/privilege.py b/ipaserver/plugins/privilege.py
index b3afbd2..01d5396 100644
--- a/ipaserver/plugins/privilege.py
+++ b/ipaserver/plugins/privilege.py
@@ -101,7 +101,7 @@ class privilege(LDAPObject):
     reverse_members = {
         'member': ['permission'],
     }
-    rdn_is_primary_key = True
+    allow_rename = True
     managed_permissions = {
         'System: Read Privileges': {
             'replaces_global_anonymous_aci': True,
diff --git a/ipaserver/plugins/radiusproxy.py b/ipaserver/plugins/radiusproxy.py
index 3391b8a..be77c62 100644
--- a/ipaserver/plugins/radiusproxy.py
+++ b/ipaserver/plugins/radiusproxy.py
@@ -101,7 +101,7 @@ class radiusproxy(LDAPObject):
         'ipatokenradiustimeout', 'ipatokenradiusretries', 'ipatokenusermapattribute'
     ]
     search_attributes = ['cn', 'description', 'ipatokenradiusserver']
-    rdn_is_primary_key = True
+    allow_rename = True
     label = _('RADIUS Servers')
     label_singular = _('RADIUS Server')
 
diff --git a/ipaserver/plugins/role.py b/ipaserver/plugins/role.py
index 5d0d1f8..e7f115c 100644
--- a/ipaserver/plugins/role.py
+++ b/ipaserver/plugins/role.py
@@ -92,7 +92,7 @@ class role(LDAPObject):
     reverse_members = {
         'member': ['privilege'],
     }
-    rdn_is_primary_key = True
+    allow_rename = True
     managed_permissions = {
         'System: Read Roles': {
             'replaces_global_anonymous_aci': True,
diff --git a/ipaserver/plugins/servicedelegation.py b/ipaserver/plugins/servicedelegation.py
index c8052e9..4f94924 100644
--- a/ipaserver/plugins/servicedelegation.py
+++ b/ipaserver/plugins/servicedelegation.py
@@ -138,7 +138,7 @@ class servicedelegation(LDAPObject):
         },
     }
 
-    rdn_is_primary_key = True
+    allow_rename = True
 
     takes_params = (
         Str(
diff --git a/ipaserver/plugins/sudorule.py b/ipaserver/plugins/sudorule.py
index 9077107..28c3f21 100644
--- a/ipaserver/plugins/sudorule.py
+++ b/ipaserver/plugins/sudorule.py
@@ -145,6 +145,7 @@ class sudorule(LDAPObject):
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
+    allow_rename = True
     attribute_members = {
         'memberuser': ['user', 'group'],
         'memberhost': ['host', 'hostgroup'],
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to