On 09/13/2012 06:40 PM, Rob Crittenden wrote:
> Martin Kosek wrote:
>> To test, add sudo commands, hosts or users to a sudo rule or hbac rule and 
>> then
>> rename or delete the linked object. After the update, the links should be
>> amended.
>>
>> ---------
>>
>> Many attributes in IPA (e.g. manager, memberuser, managedby, ...)
>> are used to store DNs of linked objects in IPA (users, hosts, sudo
>> commands, etc.). However, when the linked objects is deleted or
>> renamed, the attribute pointing to it stays with the objects and
>> thus may create a dangling link causing issues in client software
>> reading the data.
>>
>> Directory Server has a plugin to enforce referential integrity (RI)
>> by checking DEL and MODRDN operations and updating affected links.
>> It was already used for manager and secretary attributes and
>> should be expanded for the missing attributes to avoid dangling
>> links.
>>
>> As a prerequisite, all attributes checked for RI must have pres
>> and eq indexes to avoid performance issues. The following indexes
>> have been added:
>>    * manager (pres index only)
>>    * secretary (pres index only)
>>    * memberHost
>>    * memberUser
>>    * sourcehost
>>    * memberservice
>>    * managedby
>>    * memberallowcmd
>>    * memberdenycmd
>>    * ipasudorunas
>>    * ipasudorunasgroup
>>
>> Referential Integrity plugin was updated to check all these
>> attributes.
>>
>> Note: this update will only fix RI on one master as RI plugin does
>> not check replicated operations.
>>
>> https://fedorahosted.org/freeipa/ticket/2866
> 
> These patches look good but I'd like to see some tests associated with the
> referential integrity changes in patch 308. I'm not sure we need a test for
> every single combination where RI comes into play but at least testing that 
> the
> original sequence (sudorule/sudocmd) works as expected.
> 
> rob

Right, I should have seen that coming. I want this feature to be checked
properly so I added a tests for all RI-checked attributes.

Patches attached.

Martin
From f54743bc5f9d5b83b1376c99483eb38b181d2556 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 12 Sep 2012 09:28:36 +0200
Subject: [PATCH 1/4] Add attributeTypes to safe schema updater

AttributeType updates are sensitive to case, whitespace or X-ORIGIN mismatch
just like ObjectClass attribute which is already being normalized before
an update value is compared with update instructions.

Expand safe schema updater routine to cover both ObjectClasses and
AttributeTypes updates.

https://fedorahosted.org/freeipa/ticket/2440
---
 ipaserver/install/ldapupdate.py | 68 +++++++++++++++++++++++------------------
 1 file changed, 39 insertions(+), 29 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 111769ffee1d04f2036d3abe49190c715e13f99a..528e349d7975022005d2f91d70a5abed0ab42307 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -35,7 +35,7 @@ from ipalib import errors
 from ipalib import api
 from ipapython.dn import DN
 import ldap
-from ldap.schema.models import ObjectClass
+from ldap.schema.models import ObjectClass, AttributeType
 from ipapython.ipa_log_manager import *
 import krbV
 import platform
@@ -551,23 +551,32 @@ class LDAPUpdate:
             # Replacing objectClassess needs a special handling and
             # normalization of OC definitions to avoid update failures for
             # example when X-ORIGIN is the only difference
-            objectclass_replacement = False
-            if action == "replace" and entry.dn == DN(('cn', 'schema')) and \
-                    attr.lower() == "objectclasses":
-                objectclass_replacement = True
-                oid_index = {}
-                # build the OID index for replacing
-                for objectclass in entry_values:
-                    try:
-                        objectclass_object = ObjectClass(str(objectclass))
-                    except Exception, e:
-                        self.error('replace: cannot parse ObjectClass "%s": %s',
-                                        objectclass, e)
-                        continue
-                    # In a corner case, there may be more representations of
-                    # the same objectclass due to the previous updates
-                    # We want to replace them all
-                    oid_index.setdefault(objectclass_object.oid, []).append(objectclass)
+            schema_update = False
+            schema_elem_class = None
+            schema_elem_name = None
+            if action == "replace" and entry.dn == DN(('cn', 'schema')):
+                if attr.lower() == "objectclasses":
+                    schema_elem_class = ObjectClass
+                    schema_elem_name = "ObjectClass"
+                elif attr.lower() == "attributetypes":
+                    schema_elem_class = AttributeType
+                    schema_elem_name = "AttributeType"
+
+                if schema_elem_class is not None:
+                    schema_update = True
+                    oid_index = {}
+                    # build the OID index for replacing
+                    for schema_elem in entry_values:
+                        try:
+                            schema_elem_object = schema_elem_class(str(schema_elem))
+                        except Exception, e:
+                            self.error('replace: cannot parse %s "%s": %s',
+                                            schema_elem_name, schema_elem, e)
+                            continue
+                        # In a corner case, there may be more representations of
+                        # the same objectclass/attributetype due to the previous updates
+                        # We want to replace them all
+                        oid_index.setdefault(schema_elem_object.oid, []).append(schema_elem)
 
             for update_value in update_values:
                 if action == 'remove':
@@ -624,23 +633,24 @@ class LDAPUpdate:
                     except ValueError:
                         raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % update_value
                     try:
-                        if objectclass_replacement:
+                        if schema_update:
                             try:
-                                objectclass_old = ObjectClass(str(old))
+                                schema_elem_old = schema_elem_class(str(old))
                             except Exception, e:
-                                self.error('replace: cannot parse replaced ObjectClass "%s": %s',
-                                        old, e)
+                                self.error('replace: cannot parse replaced %s "%s": %s',
+                                        schema_elem_name, old, e)
                                 continue
                             replaced_values = []
-                            for objectclass in oid_index.get(objectclass_old.oid, []):
-                                objectclass_object = ObjectClass(str(objectclass))
-                                if str(objectclass_old).lower() == str(objectclass_object).lower():
+                            for schema_elem in oid_index.get(schema_elem_old.oid, []):
+                                schema_elem_object = schema_elem_class(str(schema_elem))
+                                if str(schema_elem_old).lower() == str(schema_elem_object).lower():
                                     # compare normalized values
-                                    replaced_values.append(objectclass)
-                                    self.debug('replace: replace ObjectClass "%s" with "%s"',
-                                            old, new)
+                                    replaced_values.append(schema_elem)
+                                    self.debug('replace: replace %s "%s" with "%s"',
+                                            schema_elem_name, old, new)
                             if not replaced_values:
-                                self.debug('replace: no match for replaced ObjectClass "%s"', old)
+                                self.debug('replace: no match for replaced %s "%s"',
+                                        schema_elem_name, old)
                                 continue
                             for value in replaced_values:
                                 entry_values.remove(value)
-- 
1.7.11.4

From d1ccb5bd41f631639ebba46e7de38c05bb2d666b Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 12 Sep 2012 09:34:35 +0200
Subject: [PATCH 2/4] Amend memberAllowCmd and memberDenyCmd attribute types

Attribute types of attributes designed to hold DN values are not
supposed to hold own ORDERING or SUBSTR matching rules (which were
even not correct in this case).

Update these attributes to only define an EQUALITY rule just like
other DN attribute types in IPA.

https://fedorahosted.org/freeipa/ticket/2866
---
 install/share/65ipasudo.ldif       | 4 ++--
 install/updates/10-60basev3.update | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/install/share/65ipasudo.ldif b/install/share/65ipasudo.ldif
index 7a85c8659c33794d3127d208452dcb54ad34d59e..95ab4dd3fc48d1e223abf9f88cc59e080e3ca0eb 100644
--- a/install/share/65ipasudo.ldif
+++ b/install/share/65ipasudo.ldif
@@ -6,9 +6,9 @@ dn: cn=schema
 ## ObjectClasses:       2.16.840.1.113730.3.8.8.x
 ##
 ## Attribute to store DN of an allowed SUDO command or a group of SUDO commands
-attributetypes: (2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
+attributetypes: (2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
 ## Attribute to store DN of a prohibited SUDO command or a group of SUDO commands
-attributetypes: (2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
+attributetypes: (2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
 ## Attribute to store command category
 attributeTypes: (2.16.840.1.113730.3.8.7.3 NAME 'cmdCategory' DESC 'Additional classification for commands' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 ## Attribute to store user not managed by the central server
diff --git a/install/updates/10-60basev3.update b/install/updates/10-60basev3.update
index dbd68581e7321b3d544a918bc8154e6f2ecda946..0a348150397aa50dd081e6e8a3a125feb1eee746 100644
--- a/install/updates/10-60basev3.update
+++ b/install/updates/10-60basev3.update
@@ -8,3 +8,5 @@ add:attributeTypes: (2.16.840.1.113730.3.8.11.32 NAME 'ipaKrbPrincipalAlias' DES
 add:attributeTypes: (2.16.840.1.113730.3.8.11.37 NAME 'ipaKrbAuthzData' DESC 'type of PAC preferred by a service' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3')
 add:objectClasses: (2.16.840.1.113730.3.8.12.8 NAME 'ipaKrbPrincipal' SUP krbPrincipalAux AUXILIARY MUST ( krbPrincipalName $$ ipaKrbPrincipalAlias ) X-ORIGIN 'IPA v3' )
 replace:objectClasses: ( 2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $$ managedBy ) X-ORIGIN 'IPA v2' )::( 2.16.840.1.113730.3.8.4.2 NAME 'ipaService' DESC 'IPA service objectclass' AUXILIARY MAY ( memberOf $$ managedBy $$ ipaKrbAuthzData) X-ORIGIN 'IPA v2' )
+replace:attributeTypes:( 2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )::( 2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
+replace:attributeTypes:( 2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )::( 2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
-- 
1.7.11.4

From aa411715b4e04c1598cb0cb59b437335cc073a12 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 12 Sep 2012 09:40:06 +0200
Subject: [PATCH 3/4] Run index task in ldap updater only when needed

When LDAP updater detected an update instruction in indexing tree, it run
an indexing task and waited until it ends. However, the task was run
regardless of the update instruction result. This lead to unnecessary
index tasks being defined and waited for which makes the whole LDAP
last longer.

Execute indexing task only when an index add/update instruction is
successful.

https://fedorahosted.org/freeipa/ticket/2866
---
 ipaserver/install/ldapupdate.py | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 528e349d7975022005d2f91d70a5abed0ab42307..eb95858f9f3b7e06287ac38fc6f3270ebfb70894 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -754,6 +754,8 @@ class LDAPUpdate:
 
         self.print_entity(entry, "Final value after applying updates")
 
+        added = False
+        updated = False
         if not found:
             # New entries get their orig_data set to the entry itself. We want to
             # empty that so that everything appears new when generating the
@@ -773,13 +775,13 @@ class LDAPUpdate:
                             self.info("Parent DN of %s may not exist, cannot create the entry",
                                     entry.dn)
                             return
+                added = True
                 self.modified = True
             except Exception, e:
                 self.error("Add failure %s", e)
         else:
             # Update LDAP
             try:
-                updated = False
                 changes = self.conn.generateModList(entry.origDataDict(), entry.toDict())
                 if (entry.dn == DN(('cn', 'schema'))):
                     d = dict()
@@ -805,13 +807,14 @@ class LDAPUpdate:
                 self.error("Update failed: %s", e)
                 updated = False
 
-            if (DN(('cn', 'index')) in entry.dn and
-                DN(('cn', 'userRoot')) in entry.dn):
-                taskid = self.create_index_task(entry.getValue('cn'))
-                self.monitor_index_task(taskid)
-
             if updated:
                 self.modified = True
+
+        if entry.dn.endswith(DN(('cn', 'index'), ('cn', 'userRoot'),
+                                ('cn', 'ldbm database'), ('cn', 'plugins'),
+                                ('cn', 'config'))) and (added or updated):
+            taskid = self.create_index_task(entry.getValue('cn'))
+            self.monitor_index_task(taskid)
         return
 
     def _delete_record(self, updates):
-- 
1.7.11.4

From 90a59ff5764f23873019203266ed0cceb1c1dd8c Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Wed, 12 Sep 2012 10:00:35 +0200
Subject: [PATCH 4/4] Expand Referential Integrity checks

Many attributes in IPA (e.g. manager, memberuser, managedby, ...)
are used to store DNs of linked objects in IPA (users, hosts, sudo
commands, etc.). However, when the linked objects is deleted or
renamed, the attribute pointing to it stays with the objects and
thus may create a dangling link causing issues in client software
reading the data.

Directory Server has a plugin to enforce referential integrity (RI)
by checking DEL and MODRDN operations and updating affected links.
It was already used for manager and secretary attributes and
should be expanded for the missing attributes to avoid dangling
links.

As a prerequisite, all attributes checked for RI must have pres
and eq indexes to avoid performance issues. Thus, the following
indexes are added:
  * manager (pres index only)
  * secretary (pres index only)
  * memberHost
  * memberUser
  * sourcehost
  * memberservice
  * managedby
  * memberallowcmd
  * memberdenycmd
  * ipasudorunas
  * ipasudorunasgroup

Referential Integrity plugin is updated to enforce RI for all these
attributes. Unit tests covering RI checks for all these attributes
were added as well.

Note: this update will only fix RI on one master as RI plugin does
not check replicated operations.

https://fedorahosted.org/freeipa/ticket/2866
---
 install/share/indices.ldif                |  82 +++++++++++++++++++
 install/share/referint-conf.ldif          |  28 ++++++-
 install/updates/20-indices.update         |  68 +++++++++++++++
 install/updates/25-referint.update        |  13 +++
 install/updates/Makefile.am               |   1 +
 ipaserver/install/dsinstance.py           |   2 +-
 tests/test_xmlrpc/test_hbac_plugin.py     |  27 ++++++
 tests/test_xmlrpc/test_host_plugin.py     |  54 ++++++++++++
 tests/test_xmlrpc/test_sudorule_plugin.py |  43 +++++++++-
 tests/test_xmlrpc/test_user_plugin.py     | 132 +++++++++++++++++++++++++++++-
 10 files changed, 445 insertions(+), 5 deletions(-)
 create mode 100644 install/updates/25-referint.update

diff --git a/install/share/indices.ldif b/install/share/indices.ldif
index 6233d711e1213bb29c825cfe10081aac8901bb6d..59936585cd63ec264a80d90792e1b49307da7bfa 100644
--- a/install/share/indices.ldif
+++ b/install/share/indices.ldif
@@ -41,6 +41,7 @@ objectClass:nsIndex
 cn:manager
 nsSystemIndex:false
 nsIndexType:eq
+nsIndexType:pres
 
 dn: cn=secretary,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 changetype: add
@@ -49,6 +50,7 @@ objectClass:nsIndex
 cn:secretary
 nsSystemIndex:false
 nsIndexType:eq
+nsIndexType:pres
 
 dn: cn=displayname,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 changetype: add
@@ -110,3 +112,83 @@ nsSystemIndex: false
 nsIndexType: eq
 nsIndexType: pres
 
+dn: cn=memberHost,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: memberHost
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=memberUser,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: memberUser
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=sourcehost,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: sourcehost
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=memberservice,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: memberservice
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=managedby,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: managedby
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=memberallowcmd,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: memberallowcmd
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=memberdenycmd,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: memberdenycmd
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=ipasudorunas,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: ipasudorunas
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+
+dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: ipasudorunasgroup
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
diff --git a/install/share/referint-conf.ldif b/install/share/referint-conf.ldif
index 533b97ded03fae76dd41bb9d5a4535a2744447b9..408f7598a2127a33373ec26d4f4ea1ab7ed73734 100644
--- a/install/share/referint-conf.ldif
+++ b/install/share/referint-conf.ldif
@@ -8,4 +8,30 @@ nsslapd-pluginArg7: manager
 -
 add: nsslapd-pluginArg8
 nsslapd-pluginArg8: secretary
-
+-
+add: nsslapd-pluginArg9
+nsslapd-pluginArg9: memberuser
+-
+add: nsslapd-pluginArg10
+nsslapd-pluginArg10: memberhost
+-
+add: nsslapd-pluginArg11
+nsslapd-pluginArg11: sourcehost
+-
+add: nsslapd-pluginArg12
+nsslapd-pluginArg12: memberservice
+-
+add: nsslapd-pluginArg13
+nsslapd-pluginArg13: managedby
+-
+add: nsslapd-pluginArg14
+nsslapd-pluginArg14: memberallowcmd
+-
+add: nsslapd-pluginArg15
+nsslapd-pluginArg15: memberdenycmd
+-
+add: nsslapd-pluginArg16
+nsslapd-pluginArg16: ipasudorunas
+-
+add: nsslapd-pluginArg17
+nsslapd-pluginArg17: ipasudorunasgroup
diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
index ecca027661ea7f27fa45935e3b64e7cc84b311a1..80ac66c8a17dc59de39746385b551e0c3f9af886 100644
--- a/install/updates/20-indices.update
+++ b/install/updates/20-indices.update
@@ -26,6 +26,9 @@ default:ObjectClass: nsIndex
 default:nsSystemIndex: false
 default:nsIndexType: eq
 
+dn: cn=memberHost,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+add:nsIndexType: pres
+
 dn: cn=memberUser,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 default:cn: memberUser
 default:ObjectClass: top
@@ -33,6 +36,9 @@ default:ObjectClass: nsIndex
 default:nsSystemIndex: false
 default:nsIndexType: eq
 
+dn: cn=memberUser,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+only: nsIndexType: eq,pres
+
 dn: cn=fqdn,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 default:cn: fqdn
 default:ObjectClass: top
@@ -48,3 +54,65 @@ default:ObjectClass: nsIndex
 default:nsSystemIndex: false
 default:nsIndexType: eq
 default:nsIndexType: pres
+
+dn: cn=manager,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+only: nsIndexType: eq,pres
+
+dn: cn=secretary,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+only: nsIndexType: eq,pres
+
+dn: cn=sourcehost,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: sourcehost
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=memberservice,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: memberservice
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=managedby,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: managedby
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=memberallowcmd,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: memberallowcmd
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=memberdenycmd,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: memberdenycmd
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=ipasudorunas,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: ipasudorunas
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
+
+dn: cn=ipasudorunasgroup,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: ipasudorunasgroup
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+default:nsIndexType: eq
+default:nsIndexType: pres
diff --git a/install/updates/25-referint.update b/install/updates/25-referint.update
new file mode 100644
index 0000000000000000000000000000000000000000..54f3492fae38dbc07c081678f957aaa86152294f
--- /dev/null
+++ b/install/updates/25-referint.update
@@ -0,0 +1,13 @@
+# Expand attributes checked by Referential Integrity plugin
+# pres and eq indexes defined in 20-indices.update must be set for all these
+# attributes
+dn: cn=referential integrity postoperation,cn=plugins,cn=config
+add: nsslapd-pluginArg9: memberuser
+add: nsslapd-pluginArg10: memberhost
+add: nsslapd-pluginArg11: sourcehost
+add: nsslapd-pluginArg12: memberservice
+add: nsslapd-pluginArg13: managedby
+add: nsslapd-pluginArg14: memberallowcmd
+add: nsslapd-pluginArg15: memberdenycmd
+add: nsslapd-pluginArg16: ipasudorunas
+add: nsslapd-pluginArg17: ipasudorunasgroup
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index bc7945d7a5cd77469f7fe7175ebd9da66b9119d1..9e068966530d897fe18802c9dfa13406aeb3b010 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -23,6 +23,7 @@ app_DATA =				\
 	20-winsync_index.update		\
 	21-replicas_container.update	\
 	21-ca_renewal_container.update	\
+	25-referint.update		\
 	30-s4u2proxy.update		\
 	40-delegation.update		\
 	40-dns.update			\
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 5e6aa0512fc104bc955841afc881fbd2a5f2b8f9..2c9832d0277560ea85bdc29c7167a46430c78771 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -193,7 +193,6 @@ class DsInstance(service.Service):
         self.step("creating directory server instance", self.__create_instance)
         self.step("adding default schema", self.__add_default_schemas)
         self.step("enabling memberof plugin", self.__add_memberof_module)
-        self.step("enabling referential integrity plugin", self.__add_referint_module)
         self.step("enabling winsync plugin", self.__add_winsync_module)
         self.step("configuring replication version plugin", self.__config_version_module)
         self.step("enabling IPA enrollment plugin", self.__add_enrollment_module)
@@ -204,6 +203,7 @@ class DsInstance(service.Service):
         self.step("enabling entryUSN plugin", self.__enable_entryusn)
         self.step("configuring lockout plugin", self.__config_lockout_module)
         self.step("creating indices", self.__create_indices)
+        self.step("enabling referential integrity plugin", self.__add_referint_module)
         self.step("configuring ssl for ds instance", self.__enable_ssl)
         self.step("configuring certmap.conf", self.__certmap_conf)
         self.step("configure autobind for root", self.__root_autobind)
diff --git a/tests/test_xmlrpc/test_hbac_plugin.py b/tests/test_xmlrpc/test_hbac_plugin.py
index 5ecb9014deae302404656e95bbd7b2ffd282f799..22c9b74e9805a54d801f1b5842c51896d5d377b7 100644
--- a/tests/test_xmlrpc/test_hbac_plugin.py
+++ b/tests/test_xmlrpc/test_hbac_plugin.py
@@ -547,6 +547,23 @@ class test_hbac(XMLRPC_test):
             accessruletype=u'deny',
         )
 
+    def test_n_hbacrule_links(self):
+        """
+        Test adding various links to HBAC rule
+        """
+        api.Command['hbacrule_add_sourcehost'](
+            self.rule_name, host=self.test_host, hostgroup=self.test_hostgroup
+        )
+        api.Command['hbacrule_add_service'](
+            self.rule_name, hbacsvc=self.test_service
+        )
+
+        entry = api.Command['hbacrule_show'](self.rule_name)['result']
+        assert_attr_equal(entry, 'cn', self.rule_name)
+        assert_attr_equal(entry, 'sourcehost_host', self.test_host)
+        assert_attr_equal(entry, 'sourcehost_hostgroup', self.test_hostgroup)
+        assert_attr_equal(entry, 'memberservice_hbacsvc', self.test_service)
+
     def test_y_hbacrule_zap_testing_data(self):
         """
         Clear data for HBAC plugin testing.
@@ -561,6 +578,16 @@ class test_hbac(XMLRPC_test):
         api.Command['hostgroup_del'](self.test_sourcehostgroup)
         api.Command['hbacsvc_del'](self.test_service)
 
+    def test_k_2_sudorule_referential_integrity(self):
+        """
+        Test that links in HBAC rule were removed by referential integrity plugin
+        """
+        entry = api.Command['hbacrule_show'](self.rule_name)['result']
+        assert_attr_equal(entry, 'cn', self.rule_name)
+        assert 'sourcehost_host' not in entry
+        assert 'sourcehost_hostgroup' not in entry
+        assert 'memberservice_hbacsvc' not in entry
+
     def test_z_hbacrule_del(self):
         """
         Test deleting a HBAC rule using `xmlrpc.hbacrule_del`.
diff --git a/tests/test_xmlrpc/test_host_plugin.py b/tests/test_xmlrpc/test_host_plugin.py
index b3eb3151e9ded718ff48e959349ec6e562e683f5..2010af8a31f35d4354ca5fa31cc3f35065d582b1 100644
--- a/tests/test_xmlrpc/test_host_plugin.py
+++ b/tests/test_xmlrpc/test_host_plugin.py
@@ -783,6 +783,60 @@ class test_host(Declarative):
             ),
         ),
 
+
+        dict(
+            desc='Add managedby_host %r to %r' % (fqdn3, fqdn4),
+            command=('host_add_managedby', [fqdn4], dict(host=fqdn3,),
+            ),
+            expected=dict(
+                completed=1,
+                failed=dict(
+                    managedby = dict(
+                        host=tuple(),
+                    ),
+                ),
+                result=dict(
+                    dn=dn4,
+                    fqdn=[fqdn4],
+                    description=[u'Test host 4'],
+                    l=[u'Undisclosed location 4'],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)],
+                    managedby_host=[fqdn4, fqdn3],
+                ),
+            ),
+        ),
+
+
+        dict(
+            desc='Delete %r' % fqdn3,
+            command=('host_del', [fqdn3], {}),
+            expected=dict(
+                value=fqdn3,
+                summary=u'Deleted host "%s"' % fqdn3,
+                result=dict(failed=u''),
+            ),
+        ),
+
+
+        dict(
+            desc='Retrieve %r to verify that %r is gone from managedBy' % (fqdn4, fqdn3),
+            command=('host_show', [fqdn4], {}),
+            expected=dict(
+                value=fqdn4,
+                summary=None,
+                result=dict(
+                    dn=dn4,
+                    fqdn=[fqdn4],
+                    description=[u'Test host 4'],
+                    l=[u'Undisclosed location 4'],
+                    krbprincipalname=[u'host/%s@%s' % (fqdn4, api.env.realm)],
+                    has_keytab=False,
+                    has_password=False,
+                    managedby_host=[fqdn4],
+                ),
+            ),
+        ),
+
     ]
 
 class test_host_false_pwd_change(XMLRPC_test):
diff --git a/tests/test_xmlrpc/test_sudorule_plugin.py b/tests/test_xmlrpc/test_sudorule_plugin.py
index f0e6cd34f56698dd12a3601737f78106aa9048e1..9b44065af22fd49a64e30adc73836b4c5a6f931d 100644
--- a/tests/test_xmlrpc/test_sudorule_plugin.py
+++ b/tests/test_xmlrpc/test_sudorule_plugin.py
@@ -674,7 +674,7 @@ class test_sudorule(XMLRPC_test):
             api.Command['sudorule_mod'](self.rule_name, ipasudorunasusercategory=u'')
 
     @raises(errors.MutuallyExclusiveError)
-    def test_j_sudorule_exclusiverunas(self):
+    def test_j_1_sudorule_exclusiverunas(self):
         """
         Test setting ipasudorunasusercategory='all' in an Sudo rule when there are runas users
         """
@@ -684,7 +684,32 @@ class test_sudorule(XMLRPC_test):
         finally:
             api.Command['sudorule_remove_runasuser'](self.rule_name, user=self.test_command)
 
-    def test_k_sudorule_clear_testing_data(self):
+    def test_j_2_sudorule_referential_integrity(self):
+        """
+        Test adding various links to Sudo rule
+        """
+        api.Command['sudorule_add_user'](self.rule_name, user=self.test_user)
+        api.Command['sudorule_add_runasuser'](self.rule_name, user=self.test_runasuser,
+                                              group=self.test_group)
+        api.Command['sudorule_add_runasgroup'](self.rule_name, group=self.test_group)
+        api.Command['sudorule_add_host'](self.rule_name, host=self.test_host)
+        api.Command['sudorule_add_allow_command'](self.rule_name,
+                                                  sudocmd=self.test_command)
+        api.Command['sudorule_add_deny_command'](self.rule_name,
+                                                 sudocmdgroup=self.test_sudodenycmdgroup)
+        entry = api.Command['sudorule_show'](self.rule_name)['result']
+        assert_attr_equal(entry, 'cn', self.rule_name)
+        assert_attr_equal(entry, 'memberuser_user', self.test_user)
+        assert_attr_equal(entry, 'memberallowcmd_sudocmd', self.test_command)
+        assert_attr_equal(entry, 'memberdenycmd_sudocmdgroup',
+            self.test_sudodenycmdgroup)
+        assert_attr_equal(entry, 'memberhost_host', self.test_host)
+        assert_attr_equal(entry, 'ipasudorunas_user', self.test_runasuser)
+        assert_attr_equal(entry, 'ipasudorunas_group', self.test_group)
+        assert_attr_equal(entry, 'ipasudorunasgroup_group', self.test_group)
+
+
+    def test_k_1_sudorule_clear_testing_data(self):
         """
         Clear data for Sudo rule plugin testing.
         """
@@ -697,6 +722,20 @@ class test_sudorule(XMLRPC_test):
         api.Command['sudocmdgroup_del'](self.test_sudoallowcmdgroup)
         api.Command['sudocmdgroup_del'](self.test_sudodenycmdgroup)
 
+    def test_k_2_sudorule_referential_integrity(self):
+        """
+        Test that links in Sudo rule were removed by referential integrity plugin
+        """
+        entry = api.Command['sudorule_show'](self.rule_name)['result']
+        assert_attr_equal(entry, 'cn', self.rule_name)
+        assert 'memberuser_user' not in entry
+        assert 'memberallowcmd_sudocmd' not in entry
+        assert 'memberdenycmd_sudocmdgroup' not in entry
+        assert 'memberhost_host' not in entry
+        assert 'ipasudorunas_user' not in entry
+        assert 'ipasudorunas_group' not in entry
+        assert 'ipasudorunasgroup_group' not in entry
+
     def test_l_sudorule_order(self):
         """
         Test that order uniqueness is maintained
diff --git a/tests/test_xmlrpc/test_user_plugin.py b/tests/test_xmlrpc/test_user_plugin.py
index 15a195590a69d8e166a3c0812914a56cee383bfa..63a24cd64105bdf510ff930c0adc7b9c7aa511cb 100644
--- a/tests/test_xmlrpc/test_user_plugin.py
+++ b/tests/test_xmlrpc/test_user_plugin.py
@@ -64,7 +64,7 @@ def not_upg_check(response):
 class test_user(Declarative):
 
     cleanup_commands = [
-        ('user_del', [user1, user2, renameduser1, admin2], {}),
+        ('user_del', [user1, user2, renameduser1, admin2], {'continue': True}),
         ('group_del', [group1], {}),
     ]
 
@@ -1369,6 +1369,136 @@ class test_user(Declarative):
         ),
 
         dict(
+            desc='Set %r as manager of %r' % (user1, user2),
+            command=(
+                'user_mod', [user2], dict(manager=user1)
+            ),
+            expected=dict(
+                result=dict(
+                    givenname=[u'Test'],
+                    homedirectory=[u'/home/tuser2'],
+                    loginshell=[u'/bin/sh'],
+                    sn=[u'User2'],
+                    uid=[user2],
+                    uidnumber=[fuzzy_digits],
+                    gidnumber=[fuzzy_digits],
+                    memberof_group=[group1],
+                    mail=[u'%s@%s' % (user2, api.env.domain)],
+                    nsaccountlock=False,
+                    has_keytab=False,
+                    has_password=False,
+                    manager=[user1],
+                ),
+                summary=u'Modified user "%s"' % user2,
+                value=user2,
+            ),
+        ),
+
+        dict(
+            desc='Rename "%s"' % user1,
+            command=('user_mod', [user1], dict(rename=renameduser1)),
+            expected=dict(
+                result=dict(
+                    givenname=[u'Test'],
+                    homedirectory=[u'/home/tuser1'],
+                    loginshell=[u'/bin/sh'],
+                    sn=[u'User1'],
+                    uid=[renameduser1],
+                    uidnumber=[fuzzy_digits],
+                    gidnumber=[fuzzy_digits],
+                    mail=[u'%s@%s' % (user1, api.env.domain)],
+                    memberof_group=[group1],
+                    nsaccountlock=False,
+                    has_keytab=False,
+                    has_password=False,
+                ),
+                summary=u'Modified user "%s"' % user1,
+                value=user1,
+            ),
+        ),
+
+        dict(
+            desc='Retrieve %r and check that manager is renamed' % user2,
+            command=(
+                'user_show', [user2], {'all': True}
+            ),
+            expected=dict(
+                result=dict(
+                    gecos=[u'Test User2'],
+                    givenname=[u'Test'],
+                    homedirectory=[u'/home/tuser2'],
+                    krbprincipalname=[u'tuser2@' + api.env.realm],
+                    loginshell=[u'/bin/sh'],
+                    objectclass=objectclasses.user_base,
+                    sn=[u'User2'],
+                    uid=[user2],
+                    uidnumber=[fuzzy_digits],
+                    gidnumber=[u'1000'],
+                    displayname=[u'Test User2'],
+                    cn=[u'Test User2'],
+                    mail=[u'%s@%s' % (user2, api.env.domain)],
+                    initials=[u'TU'],
+                    ipauniqueid=[fuzzy_uuid],
+                    krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm),
+                                              ('cn','kerberos'),api.env.basedn)],
+                    memberof_group=[group1],
+                    nsaccountlock=False,
+                    has_keytab=False,
+                    has_password=False,
+                    dn=get_user_dn(user2),
+                    manager=[renameduser1],
+                ),
+                value=user2,
+                summary=None,
+            ),
+        ),
+
+        dict(
+            desc='Delete %r' % renameduser1,
+            command=('user_del', [renameduser1], {}),
+            expected=dict(
+                result=dict(failed=u''),
+                summary=u'Deleted user "%s"' % renameduser1,
+                value=renameduser1,
+            ),
+        ),
+
+        dict(
+            desc='Retrieve %r and check that manager is gone' % user2,
+            command=(
+                'user_show', [user2], {'all': True}
+            ),
+            expected=dict(
+                result=dict(
+                    gecos=[u'Test User2'],
+                    givenname=[u'Test'],
+                    homedirectory=[u'/home/tuser2'],
+                    krbprincipalname=[u'tuser2@' + api.env.realm],
+                    loginshell=[u'/bin/sh'],
+                    objectclass=objectclasses.user_base,
+                    sn=[u'User2'],
+                    uid=[user2],
+                    uidnumber=[fuzzy_digits],
+                    gidnumber=[u'1000'],
+                    displayname=[u'Test User2'],
+                    cn=[u'Test User2'],
+                    mail=[u'%s@%s' % (user2, api.env.domain)],
+                    initials=[u'TU'],
+                    ipauniqueid=[fuzzy_uuid],
+                    krbpwdpolicyreference=[DN(('cn','global_policy'),('cn',api.env.realm),
+                                              ('cn','kerberos'),api.env.basedn)],
+                    memberof_group=[group1],
+                    nsaccountlock=False,
+                    has_keytab=False,
+                    has_password=False,
+                    dn=get_user_dn(user2),
+                ),
+                value=user2,
+                summary=None,
+            ),
+        ),
+
+        dict(
             desc='Reset default user group',
             command=(
                 'config_mod', [], dict(ipadefaultprimarygroup=u'ipausers'),
-- 
1.7.11.4

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to