From 28db6cd40100c6301121e3f82c074624fe53729c Mon Sep 17 00:00:00 2001 From: Stanislav Laznicka Date: Mar 27 2017 17:08:26 +0000 Subject: Reworked the renaming mechanism The rename operation on *_mod commands was only allowed when the primary key of an entry was also its RDN. With these changes, it should be possible to rename the rest of the entries as well. An attribute to the base LDAPObject was added to whitelist the objects we want to allow to be renamed. It replaced an old attribute rdn_is_primary_key which was used for the very same purpose but the name was confusing because it was not set correctly for certain objects. https://pagure.io/freeipa/issue/2466 https://pagure.io/freeipa/issue/6784 Reviewed-By: Alexander Bokovoy Reviewed-By: Jan Cholasta Reviewed-By: Martin Basti --- 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.text import _ 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 @@ class LDAPUpdate(LDAPQuery, crud.Update): 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 @@ class LDAPUpdate(LDAPQuery, crud.Update): _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 @@ class LDAPUpdate(LDAPQuery, crud.Update): 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/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(