Active Directory Schema Design Considerations and Auxiliary Classes

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm

This blog addresses one very specific but important issue around rules of creating auxiliary classes, and as most of my blogs, is based on something I stumbled upon while working on one of my projects.
The scenario I am working on involves extending Active Directory schema for the purposes of storing Oracle related information in Active Directory. For more details on the approach of synchronizing AD and Oracle security information see my previous blog on this subject: https://blogs.msdn.com/alextch/archive/2006/06/05/ADtoOra.aspx
For the purposes of this discussion what is important is that we need to store additional information in the AD user class that pertains to Oracle specific attributes.  The best practice around extending AD schema suggests creating an auxiliary class to store such information as opposed to modifying directly schema classes provided by Microsoft. The idea here is to create OracleSecurityPrincipal auxiliary class, which contains Oracle specific attributes and then later add this class as an auxiliary class to the User schema class. In this way we encapsulated all of the Oracle related information in a separate class but still have the ability to store these attributes in AD user object.
So I followed this guidance and created an LDIF import file that would create the required structure.
Below you can see an excerpt from that LDIF file.

dn: CN=codePlex-OraDefaultTableSpace,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
changetype: add
adminDisplayName: codePlex-OraDefaultTableSpace
attributeID: 1.2.840.113556.1.4.7000.159.24.10.66
attributeSyntax: 2.5.5.12
cn: codePlex-OraDefaultTableSpace
description: Specifies Oracle Default TableSpace
isMemberOfPartialAttributeSet: FALSE
isSingleValued: TRUE
lDAPDisplayName: codePlex-OraDefaultTableSpace
distinguishedName: CN=codePlex-OraDefaultTableSpace,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
objectClass: attributeSchema
oMSyntax: 64
name: codePlex-OraDefaultTableSpace
searchFlags: 0

dn: CN=codePlex-OraProfile,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
changetype: add
adminDisplayName: codePlex-OraProfile
attributeID: 1.2.840.113556.1.4.7000.159.24.10.68
attributeSyntax: 2.5.5.12
cn: codePlex-OraProfile
description: Specifies Oracle Profile Name
isMemberOfPartialAttributeSet: FALSE
isSingleValued: TRUE
lDAPDisplayName: codePlex-OraProfile
distinguishedName: CN=codePlex-OraProfile,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
objectClass: attributeSchema
oMSyntax: 64
name: codePlex-OraProfile
searchFlags: 0

DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-

dn: CN=codePlex-OraSecurityPrincipal,CN=Schema,CN=Configuration,DC=Fabrikam,DC=msft
changetype: add
adminDisplayName: codePlex-OraSecurityPrincipal
description: adds Oracle related attributes to the user class
objectCategory: CN=Class-Schema,CN=Schema,CN=Configuration,DC=Fabrikam,DC=msft
objectClass: classSchema
lDAPDisplayName: codePlex-OraSecurityPrincipal
governsID: 1.2.840.113556.1.4.7000.159.24.10.611.11
instanceType: 4
objectClassCategory: 3
subClassOf: top
mustContain: codePlex-OraDefaultTableSpace
mayContain: codePlex-OraProfile

DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-

dn: CN=User,CN=Schema,CN=Configuration,DC=fabrikam,DC=msft
changetype: ntdsSchemaModify
add: auxiliaryClass
auxiliaryClass: codePlex-OraSecurityPrincipal
-

DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-

So let’s quickly walk through this LDIF file.
Firstly, we created several Oracle related attributes (there are many more required attributes, but I removed them to conserve space).
Secondly, an auxiliary schema class is created and the attributes created in step 1 are attached to it via mustContain and mayContain attributes of this class. We will come back to those two attributes shortly.
Thirdly, we add the auxiliary class created in step to 2 to the user schema object.

When I put together this LDIF file it seemed pretty logical to me, but when I ran this file against my test environment I got the following error message “Unwilling to perform. The server side error is 8505.” This error occurred while trying to add the OracleSecurityPrincipal auxiliary class into User structured class.

After conducting some research I realized that the issue was around the mandatory attributes defined as part of the auxiliary class via mustContain attribute. During addition of an auxiliary class AD checks if there are any existing objects of the structured class that we are modifying (in our case we are dealing with the User class, and of course any AD deployment would have existing user objects), but since I defined codePlex-oraDefaultTableSpace as a mandatory attribute, this would create an integrity issue within AD where some object that were created previously would be missing some mandatory attributes, and therefore AD refused this change.
So now that we understand the issue at hand, the only way to fix this issue is to make all of the auxiliary class attributes optional, by specifying them as mayContain.
mustContain: codePlex-OraDefaultTableSpace
mayContain: codePlex-OraProfile
Even though from the logic of the OracleSecurityPrincipal class the OraProfile attribute may be considered optional and OraDefaultTableSpace mandatory.
This was not intuitive to me at the beginning, but I suppose this is something that we need to keep in mind when working with auxiliary classes, especially if we plan to add them into the structured classes that already have objects in AD.