/// <summary> /// Adds a set of object definitions to the schema. The format of the schema /// is a sequence of lines where objects are separated by empty lines. /// Each line of an object definition is a pair of an attribute display name and an /// attribute value, separated by ':'. Takes care of "bootstrapping" the contents of /// the schema, resolving cyclic dependencies between attribute definitions and objects. /// </summary> /// <param name="schema">The schema, represented as explained above.</param> /// <param name="valueSubstitution">A substitution of strings which should be applied to each value in the /// schema. For example, may map '<rootdomaindn>' as used in schemas to represent the root name of the /// current DC to an actual value</param> /// <param name="serverVersion">Specify the OS version of the server</param> /// <returns>Result of the Load Schema method.</returns> public ModelResult LoadSchema(IEnumerable<string> schema, Map<string, string> valueSubstitution, OSVersion serverVersion) { SequenceContainer<ModelObject> newObjects = new SequenceContainer<ModelObject>(); UnresolvedSyntax unresolvedSyntax = new UnresolvedSyntax(); #region Pass 1: read in objects resolving only builtin attributes // We need this phase for bootstrapping, as we don't have the // attributes available which are used in the objects. Thus // in this phase, we just parse attribute values of a certain set // of builting attributes which are required to get attribute syntax // definitions. InitializeBuiltinAttributes(); ModelObject tempobj = null; ModelObject obj = null; foreach (string rawLine in schema) { string line = (rawLine == null)?null:rawLine.Trim(); if (String.IsNullOrEmpty(line)) { // Object ends here if (obj != null) { if (obj[StandardNames.attributeID] == null && obj[StandardNames.governsId] == null) { if (obj.attributes.Count == 1) { // If the server is Windows 2000 if (serverVersion == OSVersion.WinSvr2000) { string attr = obj.attributes.Keys.ElementAt(0); if (tempobj.attributes.Keys.Contains(attr)) { tempobj.attributes.Keys.Remove(attr); tempobj.attributes.Add(obj.attributes.ElementAt(0)); } else { tempobj.attributes.Add(obj.attributes.ElementAt(0)); } newObjects.RemoveAt(newObjects.Count - 1); newObjects.Add(tempobj); } } else { Checks.Fail("attributeId/governsId is mandatory for each object {0}.", obj[StandardNames.cn]); } } else if (obj[StandardNames.attributeID] != null) { AddAttribute(obj); newObjects.Add(obj); } else if (obj[StandardNames.governsId] != null) { // This is a class definition. Fill in class map. AddClass(obj); newObjects.Add(obj); } tempobj = obj; obj = null; } } else { if (obj == null) { obj = new ModelObject(); } obj.dc = this; string attr, valueString; string[] splits = line.Split(new char[] { ':' }, 2); if (splits == null || splits.Length != 2) { Checks.Fail("invalid schema line '{0}'", line); continue; } attr = splits[0].Trim().ToLower(); valueString = Substitute(valueSubstitution, splits[1].Trim()); AttributeContext parsingContext; if (!builtinAttributeSyntax.TryGetValue(attr, out parsingContext)) { parsingContext = new AttributeContext(this, attr, unresolvedSyntax); } obj[attr] = parsingContext.Parse(valueString); } } #endregion #region Pass 2: resolve syntax of all new objects // As we have now attribute definitions which map to the syntax, we can parse // All values in the new objects. foreach (ModelObject newObj in newObjects) { foreach (string attr in newObj.attributes.Keys) { AttributeContext parsingContext = GetAttributeContext(attr); Value value = newObj[attr]; if (value.Syntax is UnresolvedSyntax) { newObj[attr] = parsingContext.Parse((string)value); } else { Checks.IsTrue( parsingContext.syntax == value.Syntax, "bultin syntax assignment of '{0}' must match syntax declared in schema", attr); } } } #endregion #region Pass 3: add objects // Now all definitions are complete. Add them to schema replica root. foreach (ModelObject newObj in newObjects) { AddChild(schemaReplica.root, newObj); } #endregion #region Pass 4: check consistency // The tree is finally setup. Now check for consistency Consistency.Check(newObjects.ToSequence()); #endregion if (Checks.HasDiagnostics) { return new ModelResult(ResultCode.ConstraintViolation, Checks.GetAndClearLog(), Checks.GetAndClearDiagnostics()); } else { //return Result.Success; ModelResult tempRes = new ModelResult(ResultCode.Success); tempRes.logMessage = Checks.GetAndClearLog(); return tempRes; } }
/// <summary> /// Content rules determine the mandatory and optional attributes of the class instances that are stored /// in the directory. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckContent(ModelObject obj) { List <string> mustContain = new List <string>(); List <string> mayContain = new List <string>(); string[] ExcludeAttributes = { "instancetype", "ntsecuritydescriptor", "objectcategory", "objectsid" }; // Compute sets of may/must attribute string className = (string)obj[StandardNames.objectClass].UnderlyingValues.Last(); List <string> superClassList = GetSuperClassList(obj.dc, className); foreach (string classId in superClassList) { ModelObject classObj; if (!obj.dc.TryGetClass(classId, out classObj)) { // will be reported in a different checker continue; } mustContain.AddRange(GetAttributeSet(classObj, StandardNames.mustContain)); mustContain.AddRange(GetAttributeSet(classObj, StandardNames.systemMustContain)); mayContain.AddRange(GetAttributeSet(classObj, StandardNames.mayContain)); mayContain.AddRange(GetAttributeSet(classObj, StandardNames.systemMayContain)); } //Removing default excluded attributes from this list. foreach (string attr in ExcludeAttributes) { if (mustContain.Contains(attr)) { mustContain.Remove(attr); } } // Check for all attributes string dn = (string)obj[StandardNames.shortName]; //For each attribute foreach (string attr in obj.attributes.Keys) { if (!ExcludeAttributes.Contains(attr)) { //This attribute is in must contain list. if (mustContain.Contains(attr)) { mustContain.Remove(attr); } //This attribute is not in must and may contian list. else if (!mayContain.Contains(attr)) { Checks.Fail("'{0}' must not contain attribute '{1}'", dn, attr); } } } //If must contain list is not empty, this object does not contain some must contain attributes. if (mustContain.Count > 0) { Checks.Fail( "'{0}' does not contain required attributes '{1}'", dn, String.Join(",", mustContain.ToArray())); } }
/// <summary> /// Inheritance is the ability to build new classes from existing classes. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckInheritance(ModelObject obj) { string dn = (string)obj[StandardNames.shortName]; //Get super class id. string superClassId = (string)obj[StandardNames.subClassOf]; if (superClassId == null) { Checks.Fail("'{0}' must have a super class", dn); } else { ModelObject superClass; if (!obj.dc.TryGetClass(superClassId, out superClass)) { Checks.Fail("'{0}' has undefined super class '{1}'", dn, superClassId); } else { string thisClassId = (string)obj[StandardNames.governsId]; if (thisClassId != StandardNames.topGovernsId && thisClassId == superClassId) { Checks.Fail("{0} cannot inherit from itself", dn); } //Get super class and current class category. ObjectClassCategory thisCategory = (ObjectClassCategory)(int)obj[StandardNames.objectClassCategory]; ObjectClassCategory superCategory = (ObjectClassCategory)(int)superClass[StandardNames.objectClassCategory]; bool categoryOk; switch (thisCategory) { //Abstract Class must be inherited from Abstract Class. case ObjectClassCategory.AbstractClass: categoryOk = superCategory == ObjectClassCategory.AbstractClass; break; //Auxiliary Class must not be inherited from Structural Class case ObjectClassCategory.AuxiliaryClass: categoryOk = superCategory != ObjectClassCategory.StructuralClass; break; //Structural Class must not be inherited from Auxiliary Class case ObjectClassCategory.StructuralClass: categoryOk = superCategory != ObjectClassCategory.AuxiliaryClass; break; //Default. default: categoryOk = true; break; } if (!categoryOk) { Checks.Fail("{0} must have object class category which is compatible with its super class", dn); } } } }
/// <summary> /// Check objectClass. /// </summary> /// <param name="obj">ModelObject.</param> public static void CheckObjectClass(ModelObject obj) { string dn = (string)obj[StandardNames.shortName]; Value objectClass = obj[StandardNames.objectClass]; if (objectClass == null) { Checks.Fail("'{0}' must have object class", dn); return; } // Check for resolution and last/first element compliance int count = 0; List <string> classes = obj.GetAllClassIds().ToList(); bool resolveOk = true; foreach (string classId in classes) { ModelObject classObject; //The object must be present in Schema NC. if (!obj.dc.TryGetClass(classId, out classObject)) { Checks.Fail("'{0}' has undefined object class '{1}'", dn, classId); resolveOk = false; continue; } //Checking for the first class is Top if (count == 0 && classId != StandardNames.top) { Checks.Fail("'{0}' first object class must be top", dn); resolveOk = false; } //Checking for the last class is Structural class. if (count == objectClass.UnderlyingValues.Count - 1) { ObjectClassCategory category = (ObjectClassCategory)(int)classObject[StandardNames.objectClassCategory]; if (category != ObjectClassCategory.StructuralClass) { Checks.Fail("'{0}' last object class must be structural"); resolveOk = false; } } count++; } if (!resolveOk) { return; } // Check for superclass chaining // Checks that if the value of object class is [top,ac_1,...,ac_n,sc_1,...,sc_n], // then sc_n is the most specific structural class, sc_1...scn_(n-1) is the super class chain of sc_n (excluding top), // and ac_n is the next auxilary class before that, where ac_1...acn(n-1) is that classes chain excluding classes // which have been seen already before (where there can be a number of ac anchors here). List <string> includedClasses = new List <string>(); int i = classes.Count - 1; while (i > 0) { string classId = classes[i--]; if (includedClasses.Contains(classId)) { Checks.Fail("'{0}' object class contains repeated entries", dn); break; } includedClasses.Add(classId); ObjectClassCategory category = (ObjectClassCategory)(int)obj.dc.GetClass(classId)[StandardNames.objectClassCategory]; if (i == classes.Count - 2) { // Most specific class must be structural if (category != ObjectClassCategory.StructuralClass) { Checks.Fail("'{0}' object class most specific class must be structural", dn); } } //If the server is Pre Windows 2003. foreach (string clId in GetSuperClassChain(obj.dc, classId)) { if (includedClasses.Contains(clId)) { // Already included in a previous chain continue; } includedClasses.Add(clId); if (i <= 0) { Checks.Fail( "'{0}' object class does not contain chain of super classes of '{1}' (classes missing)", dn, classId); break; } if (classes[i--] != clId) { Checks.Fail( "'{0}' object class does not contain chain of super classes '{1}' (found unexpected '{2}')", dn, classId, classes[i + 1]); break; } } } }
/// <summary> /// If it is a structural object class, then the objectClass attribute of objects of the class does not contain /// the name of the auxiliary class aux. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckStructure(ModelObject obj) { //Collect possSuperiors. List <object> possSuperiors = new List <object>(); //Get current class name. string className = obj[StandardNames.ldapDisplayName].UnderlyingValues.ElementAt(0).ToString(); //Get super class list. List <string> superClassList = GetSuperClassList(obj.dc, className); //For each super class foreach (string superClass in superClassList) { //Get class object of this class name. ModelObject classObject = obj.dc.GetClass(superClass); //Get possSuperiors. if (classObject[StandardNames.possSuperiors] != null) { possSuperiors.AddRange(classObject[StandardNames.possSuperiors].UnderlyingValues); } //Get systemPossSuperiors. if (classObject[StandardNames.systemPossSuperiors] != null) { possSuperiors.AddRange(classObject[StandardNames.systemPossSuperiors].UnderlyingValues); } } //For each poss superior class name... foreach (string possSuperior in possSuperiors) { #region two //Get the poss superior object and possibleInferiors attribute. ModelObject superiorObj = obj.dc.GetClass(possSuperior); ModelObject attrObject = superiorObj.dc.GetAttribute(StandardNames.possibleInferiors); if (superiorObj != null) { AttributeContext parsingContext = obj.dc.GetAttributeContext(StandardNames.possibleInferiors); //If this class alrady contains the possibleInferiors... if (superiorObj[StandardNames.possibleInferiors] != null) { string valueString = String.Empty; foreach (object val in superiorObj[StandardNames.possibleInferiors].UnderlyingValues) { valueString = valueString + val.ToString() + ";"; } //Append the current class name with already existing possibleInferiors attribute. valueString = valueString + className; superiorObj[StandardNames.possibleInferiors] = parsingContext.Parse(valueString); } else { //Create a new possibleInferiors attribute with the cureent class name. superiorObj[StandardNames.possibleInferiors] = parsingContext.Parse(className); } } else { Checks.Fail( "{0} class's possSuperior {1} is not in Schema NC.", className, superiorObj.attributes[StandardNames.cn].UnderlyingValues.ElementAt(0).ToString()); } #endregion } }