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 02ed0dd364558e387b09c2c407119e04cf93f982 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 primary key in their DN.

Also added tests for renaming sudo and HBAC rules.

https://pagure.io/freeipa/issue/2466
https://pagure.io/freeipa/issue/6784
---
 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 +
 ipatests/test_xmlrpc/test_hbac_plugin.py     | 15 +++++++++++++
 ipatests/test_xmlrpc/test_sudorule_plugin.py | 14 ++++++++++++
 19 files changed, 71 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'],
diff --git a/ipatests/test_xmlrpc/test_hbac_plugin.py b/ipatests/test_xmlrpc/test_hbac_plugin.py
index 75c15c5..b495fe3 100644
--- a/ipatests/test_xmlrpc/test_hbac_plugin.py
+++ b/ipatests/test_xmlrpc/test_hbac_plugin.py
@@ -34,6 +34,7 @@ class test_hbac(XMLRPC_test):
     Test the `hbacrule` plugin.
     """
     rule_name = u'testing_rule1234'
+    rule_renamed = u'mega_testing_rule'
     rule_type = u'allow'
     rule_type_fail = u'value not allowed'
     rule_service = u'ssh'
@@ -459,6 +460,20 @@ def test_n_hbacrule_links(self):
         assert_attr_equal(entry, 'cn', self.rule_name)
         assert_attr_equal(entry, 'memberservice_hbacsvc', self.test_service)
 
+    def test_o_hbacrule_rename(self):
+        """
+        Test renaming an HBAC rule, rename it back afterwards
+        """
+        api.Command['hbacrule_mod'](
+            self.rule_name, rename=self.rule_renamed
+        )
+        entry = api.Command['hbacrule_show'](self.rule_renamed)['result']
+        assert_attr_equal(entry, 'cn', self.rule_renamed)
+        # clean up by renaming the rule back
+        api.Command['hbacrule_mod'](
+            self.rule_renamed, rename=self.rule_name
+        )
+
     def test_y_hbacrule_zap_testing_data(self):
         """
         Clear data for HBAC plugin testing.
diff --git a/ipatests/test_xmlrpc/test_sudorule_plugin.py b/ipatests/test_xmlrpc/test_sudorule_plugin.py
index c37262a..7222a3a 100644
--- a/ipatests/test_xmlrpc/test_sudorule_plugin.py
+++ b/ipatests/test_xmlrpc/test_sudorule_plugin.py
@@ -42,6 +42,7 @@ class test_sudorule(XMLRPC_test):
     """
     rule_name = u'testing_sudorule1'
     rule_name2 = u'testing_sudorule2'
+    rule_renamed = u'testing_mega_sudorule'
     rule_command = u'/usr/bin/testsudocmd1'
     rule_desc = u'description'
     rule_desc_mod = u'description modified'
@@ -782,6 +783,19 @@ def test_l_sudorule_order(self):
         api.Command['sudorule_mod'](self.rule_name, sudoorder=None)
         api.Command['sudorule_mod'](self.rule_name2, sudoorder=None)
 
+     def test_l_1_sudorule_rename(self):
+         """
+         Test renaming an HBAC rule, rename it back afterwards
+         """
+         api.Command['sudorule_mod'](
+             self.rule_name, rename=self.rule_renamed
+         )
+         entry = api.Command['sudorule_show'](self.rule_renamed)['result']
+         assert_attr_equal(entry, 'cn', self.rule_renamed)
+         # clean up by renaming the rule back
+         api.Command['sudorule_mod'](
+             self.rule_renamed, rename=self.rule_name
+         )
 
     def test_m_sudorule_del(self):
         """
-- 
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