// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; namespace System.DirectoryServices.ActiveDirectory { internal enum SearchFlags : int { None = 0, IsIndexed = 1, IsIndexedOverContainer = 2, IsInAnr = 4, IsOnTombstonedObject = 8, IsTupleIndexed = 32 } public class ActiveDirectorySchemaProperty : IDisposable { // private variables private DirectoryEntry? _schemaEntry; private DirectoryEntry? _propertyEntry; private DirectoryEntry? _abstractPropertyEntry; private readonly NativeComInterfaces.IAdsProperty? _iadsProperty; private readonly DirectoryContext _context; internal bool isBound; private bool _disposed; private ActiveDirectorySchema? _schema; private bool _propertiesFromSchemaContainerInitialized; private bool _isDefunctOnServer; private SearchResult? _propertyValuesFromServer; // private variables for caching properties private readonly string _ldapDisplayName; private string? _commonName; private string? _oid; private ActiveDirectorySyntax _syntax = (ActiveDirectorySyntax)(-1); private bool _syntaxInitialized; private string? _description; private bool _descriptionInitialized; private bool _isSingleValued; private bool _isSingleValuedInitialized; private bool _isInGlobalCatalog; private bool _isInGlobalCatalogInitialized; private Nullable<int> _rangeLower; private bool _rangeLowerInitialized; private Nullable<int> _rangeUpper; private bool _rangeUpperInitialized; private bool _isDefunct; private SearchFlags _searchFlags = SearchFlags.None; private bool _searchFlagsInitialized; private ActiveDirectorySchemaProperty? _linkedProperty; private bool _linkedPropertyInitialized; private Nullable<int> _linkId; private bool _linkIdInitialized; private byte[]? _schemaGuidBinaryForm; // OMObjectClass values for the syntax //0x2B0C0287731C00854A private static readonly OMObjectClass s_dnOMObjectClass = new OMObjectClass(new byte[] { 0x2B, 0x0C, 0x02, 0x87, 0x73, 0x1C, 0x00, 0x85, 0x4A }); //0x2A864886F7140101010C private static readonly OMObjectClass s_dNWithStringOMObjectClass = new OMObjectClass(new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x14, 0x01, 0x01, 0x01, 0x0C }); //0x2A864886F7140101010B private static readonly OMObjectClass s_dNWithBinaryOMObjectClass = new OMObjectClass(new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x14, 0x01, 0x01, 0x01, 0x0B }); //0x2A864886F71401010106 private static readonly OMObjectClass s_replicaLinkOMObjectClass = new OMObjectClass(new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x14, 0x01, 0x01, 0x01, 0x06 }); //0x2B0C0287731C00855C private static readonly OMObjectClass s_presentationAddressOMObjectClass = new OMObjectClass(new byte[] { 0x2B, 0x0C, 0x02, 0x87, 0x73, 0x1C, 0x00, 0x85, 0x5C }); //0x2B0C0287731C00853E private static readonly OMObjectClass s_accessPointDnOMObjectClass = new OMObjectClass(new byte[] { 0x2B, 0x0C, 0x02, 0x87, 0x73, 0x1C, 0x00, 0x85, 0x3E }); //0x56060102050B1D private static readonly OMObjectClass s_oRNameOMObjectClass = new OMObjectClass(new byte[] { 0x56, 0x06, 0x01, 0x02, 0x05, 0x0B, 0x1D }); // syntaxes private static readonly Syntax[] s_syntaxes = {/* CaseExactString */ new Syntax("2.5.5.3", 27, null), /* CaseIgnoreString */ new Syntax("2.5.5.4", 20, null), /* NumericString */ new Syntax("2.5.5.6", 18, null), /* DirectoryString */ new Syntax("2.5.5.12", 64, null), /* OctetString */ new Syntax("2.5.5.10", 4, null), /* SecurityDescriptor */ new Syntax("2.5.5.15", 66, null), /* Int */ new Syntax("2.5.5.9", 2, null), /* Int64 */ new Syntax("2.5.5.16", 65, null), /* Bool */ new Syntax("2.5.5.8", 1, null), /* Oid */ new Syntax("2.5.5.2", 6, null), /* GeneralizedTime */ new Syntax("2.5.5.11", 24, null), /* UtcTime */ new Syntax("2.5.5.11", 23, null), /* DN */ new Syntax("2.5.5.1", 127, s_dnOMObjectClass), /* DNWithBinary */ new Syntax("2.5.5.7", 127, s_dNWithBinaryOMObjectClass), /* DNWithString */ new Syntax("2.5.5.14", 127, s_dNWithStringOMObjectClass), /* Enumeration */ new Syntax("2.5.5.9", 10, null), /* IA5String */ new Syntax("2.5.5.5", 22, null), /* PrintableString */ new Syntax("2.5.5.5", 19, null), /* Sid */ new Syntax("2.5.5.17", 4, null), /* AccessPointDN */ new Syntax("2.5.5.14", 127, s_accessPointDnOMObjectClass), /* ORName */ new Syntax("2.5.5.7", 127, s_oRNameOMObjectClass), /* PresentationAddress */ new Syntax("2.5.5.13", 127, s_presentationAddressOMObjectClass), /* ReplicaLink */ new Syntax("2.5.5.10", 127, s_replicaLinkOMObjectClass)}; #region constructors public ActiveDirectorySchemaProperty(DirectoryContext context, string ldapDisplayName) { ArgumentNullException.ThrowIfNull(context); if ((context.Name == null) && (!context.isRootDomain())) { throw new ArgumentException(SR.ContextNotAssociatedWithDomain, nameof(context)); } if (context.Name != null) { // the target should be a valid forest name or a server if (!((context.isRootDomain()) || (context.isADAMConfigSet()) || (context.isServer()))) { throw new ArgumentException(SR.NotADOrADAM, nameof(context)); } } ArgumentNullException.ThrowIfNull(ldapDisplayName); if (ldapDisplayName.Length == 0) { throw new ArgumentException(SR.EmptyStringParameter, nameof(ldapDisplayName)); } _context = new DirectoryContext(context); // validate the context _schemaEntry = DirectoryEntryManager.GetDirectoryEntry(context, WellKnownDN.SchemaNamingContext); _schemaEntry.Bind(true); _ldapDisplayName = ldapDisplayName; // common name of the property defaults to the ldap display name _commonName = ldapDisplayName; // set the bind flag this.isBound = false; } // internal constructor internal ActiveDirectorySchemaProperty(DirectoryContext context, string ldapDisplayName, DirectoryEntry? propertyEntry, DirectoryEntry? schemaEntry) { _context = context; _ldapDisplayName = ldapDisplayName; // common name of the property defaults to the ldap display name _propertyEntry = propertyEntry; _isDefunctOnServer = false; _isDefunct = _isDefunctOnServer; try { // initialize the directory entry for the abstract schema class _abstractPropertyEntry = DirectoryEntryManager.GetDirectoryEntryInternal(context, "LDAP://" + context.GetServerName() + "/schema/" + ldapDisplayName); _iadsProperty = (NativeComInterfaces.IAdsProperty)_abstractPropertyEntry.NativeObject; } catch (COMException e) { if (e.ErrorCode == unchecked((int)0x80005000)) { throw new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySchemaProperty), ldapDisplayName); } else { throw ExceptionHelper.GetExceptionFromCOMException(context, e); } } catch (InvalidCastException) { // this means that we found an object but it is not a schema class throw new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySchemaProperty), ldapDisplayName); } catch (ActiveDirectoryObjectNotFoundException) { // this is the case where the context is a config set and we could not find an ADAM instance in that config set throw new ActiveDirectoryOperationException(SR.Format(SR.ADAMInstanceNotFoundInConfigSet, context.Name)); } // set the bind flag this.isBound = true; } internal ActiveDirectorySchemaProperty(DirectoryContext context, string commonName, SearchResult propertyValuesFromServer, DirectoryEntry schemaEntry) { _context = context; _schemaEntry = schemaEntry; // all relevant properties have already been retrieved from the server _propertyValuesFromServer = propertyValuesFromServer; Debug.Assert(_propertyValuesFromServer != null); _propertiesFromSchemaContainerInitialized = true; _propertyEntry = GetSchemaPropertyDirectoryEntry(); // names _commonName = commonName; _ldapDisplayName = (string)GetValueFromCache(PropertyManager.LdapDisplayName, true)!; // this constructor is only called for defunct classes _isDefunctOnServer = true; _isDefunct = _isDefunctOnServer; // set the bind flag this.isBound = true; } internal ActiveDirectorySchemaProperty(DirectoryContext context, string commonName, string ldapDisplayName, DirectoryEntry propertyEntry, DirectoryEntry schemaEntry) { _context = context; _schemaEntry = schemaEntry; _propertyEntry = propertyEntry; // names _commonName = commonName; _ldapDisplayName = ldapDisplayName; // this constructor is only called for defunct properties _isDefunctOnServer = true; _isDefunct = _isDefunctOnServer; // set the bind flag this.isBound = true; } #endregion constructors #region IDisposable public void Dispose() { Dispose(true); } // private Dispose method protected virtual void Dispose(bool disposing) { if (!_disposed) { // check if this is an explicit Dispose // only then clean up the directory entries if (disposing) { // dispose schema entry if (_schemaEntry != null) { _schemaEntry.Dispose(); _schemaEntry = null; } // dispose property entry if (_propertyEntry != null) { _propertyEntry.Dispose(); _propertyEntry = null; } // dispose abstract class entry if (_abstractPropertyEntry != null) { _abstractPropertyEntry.Dispose(); _abstractPropertyEntry = null; } // dispose the schema object _schema?.Dispose(); } _disposed = true; } } #endregion IDisposable #region public methods public static ActiveDirectorySchemaProperty FindByName(DirectoryContext context, string ldapDisplayName) { ArgumentNullException.ThrowIfNull(context); ActiveDirectorySchemaProperty? schemaProperty = null; if ((context.Name == null) && (!context.isRootDomain())) { throw new ArgumentException(SR.ContextNotAssociatedWithDomain, nameof(context)); } if (context.Name != null) { // the target should be a valid forest name or a server if (!((context.isRootDomain()) || (context.isADAMConfigSet()) || (context.isServer()))) { throw new ArgumentException(SR.NotADOrADAM, nameof(context)); } } ArgumentNullException.ThrowIfNull(ldapDisplayName); if (ldapDisplayName.Length == 0) { throw new ArgumentException(SR.EmptyStringParameter, nameof(ldapDisplayName)); } // work with copy of the context context = new DirectoryContext(context); // create a schema property schemaProperty = new ActiveDirectorySchemaProperty(context, ldapDisplayName, (DirectoryEntry?)null, null); return schemaProperty; } public void Save() { CheckIfDisposed(); if (!isBound) { try { // create a new directory entry for this class _schemaEntry ??= DirectoryEntryManager.GetDirectoryEntry(_context, WellKnownDN.SchemaNamingContext); // this will create the class and set the CN value string rdn = "CN=" + _commonName; rdn = Utils.GetEscapedPath(rdn); _propertyEntry = _schemaEntry.Children.Add(rdn, "attributeSchema"); } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } catch (ActiveDirectoryObjectNotFoundException) { // this is the case where the context is a config set and we could not find an ADAM instance in that config set throw new ActiveDirectoryOperationException(SR.Format(SR.ADAMInstanceNotFoundInConfigSet, _context.Name)); } // set the ldap display name property SetProperty(PropertyManager.LdapDisplayName, _ldapDisplayName); // set the oid value SetProperty(PropertyManager.AttributeID, _oid); // set the syntax if (_syntax != (ActiveDirectorySyntax)(-1)) { SetSyntax(_syntax); } // set the description SetProperty(PropertyManager.Description, _description); // set the isSingleValued attribute _propertyEntry.Properties[PropertyManager.IsSingleValued].Value = _isSingleValued; // set the isGlobalCatalogReplicated attribute _propertyEntry.Properties[PropertyManager.IsMemberOfPartialAttributeSet].Value = _isInGlobalCatalog; // set the isDefunct attribute _propertyEntry.Properties[PropertyManager.IsDefunct].Value = _isDefunct; // set the range lower attribute if (_rangeLower != null) { _propertyEntry.Properties[PropertyManager.RangeLower].Value = (int)_rangeLower.Value; } // set the range upper attribute if (_rangeUpper != null) { _propertyEntry.Properties[PropertyManager.RangeUpper].Value = (int)_rangeUpper.Value; } // set the searchFlags attribute if (_searchFlags != SearchFlags.None) { _propertyEntry.Properties[PropertyManager.SearchFlags].Value = (int)_searchFlags; } // set the link id if (_linkId != null) { _propertyEntry.Properties[PropertyManager.LinkID].Value = (int)_linkId.Value; } // set the schemaIDGuid property if (_schemaGuidBinaryForm != null) { SetProperty(PropertyManager.SchemaIDGuid, _schemaGuidBinaryForm); } } try { // commit the classEntry to server _propertyEntry!.CommitChanges(); // Refresh the schema cache on the schema role owner if (_schema == null) { ActiveDirectorySchema schemaObject = ActiveDirectorySchema.GetSchema(_context); bool alreadyUsingSchemaRoleOwnerContext = false; DirectoryServer? schemaRoleOwner = null; try { // // if we are not already talking to the schema role owner, change the context // schemaRoleOwner = schemaObject.SchemaRoleOwner; if (Utils.Compare(schemaRoleOwner.Name, _context.GetServerName()) != 0) { DirectoryContext schemaRoleOwnerContext = Utils.GetNewDirectoryContext(schemaRoleOwner.Name, DirectoryContextType.DirectoryServer, _context); _schema = ActiveDirectorySchema.GetSchema(schemaRoleOwnerContext); } else { alreadyUsingSchemaRoleOwnerContext = true; _schema = schemaObject; } } finally { schemaRoleOwner?.Dispose(); if (!alreadyUsingSchemaRoleOwnerContext) { schemaObject.Dispose(); } } } _schema.RefreshSchema(); } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } // now that the changes are committed to the server // update the defunct/non-defunct status of the class on the server _isDefunctOnServer = _isDefunct; // invalidate all properties _commonName = null; _oid = null; _syntaxInitialized = false; _descriptionInitialized = false; _isSingleValuedInitialized = false; _isInGlobalCatalogInitialized = false; _rangeLowerInitialized = false; _rangeUpperInitialized = false; _searchFlagsInitialized = false; _linkedPropertyInitialized = false; _linkIdInitialized = false; _schemaGuidBinaryForm = null; _propertiesFromSchemaContainerInitialized = false; // set bind flag isBound = true; } public override string ToString() => Name; public DirectoryEntry GetDirectoryEntry() { CheckIfDisposed(); if (!isBound) { throw new InvalidOperationException(SR.CannotGetObject); } GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); return DirectoryEntryManager.GetDirectoryEntryInternal(_context, _propertyEntry.Path); } #endregion public methods #region public properties public string Name { get { CheckIfDisposed(); return _ldapDisplayName; } } public string? CommonName { get { CheckIfDisposed(); if (isBound) { // get the property from the server _commonName ??= (string)GetValueFromCache(PropertyManager.Cn, true)!; } return _commonName; } set { CheckIfDisposed(); if (value != null && value.Length == 0) throw new ArgumentException(SR.EmptyStringParameter, nameof(value)); if (isBound) { // set the value on the directory entry SetProperty(PropertyManager.Cn, value); } _commonName = value; } } public string? Oid { get { CheckIfDisposed(); if (isBound) { if (_oid == null) { // get the property from the abstract schema/ schema container // (for non-defunt classes this property is available in the abstract schema) if (!_isDefunctOnServer) { try { _oid = _iadsProperty!.OID; } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } } else { _oid = (string)GetValueFromCache(PropertyManager.AttributeID, true)!; } } } return _oid; } set { CheckIfDisposed(); if (value != null && value.Length == 0) throw new ArgumentException(SR.EmptyStringParameter, nameof(value)); if (isBound) { // set the value on the directory entry SetProperty(PropertyManager.AttributeID, value); } _oid = value; } } public ActiveDirectorySyntax Syntax { get { CheckIfDisposed(); if (isBound) { if (!_syntaxInitialized) { byte[]? omObjectClassBinaryForm = (byte[]?)GetValueFromCache(PropertyManager.OMObjectClass, false); OMObjectClass? omObjectClass = (omObjectClassBinaryForm != null) ? new OMObjectClass(omObjectClassBinaryForm) : null; _syntax = MapSyntax((string)GetValueFromCache(PropertyManager.AttributeSyntax, true)!, (int)GetValueFromCache(PropertyManager.OMSyntax, true)!, omObjectClass); _syntaxInitialized = true; } } return _syntax; } set { CheckIfDisposed(); if (value < ActiveDirectorySyntax.CaseExactString || value > ActiveDirectorySyntax.ReplicaLink) { throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(ActiveDirectorySyntax)); } if (isBound) { // set the value on the directory entry SetSyntax(value); } _syntax = value; } } public string? Description { get { CheckIfDisposed(); if (isBound) { if (!_descriptionInitialized) { // get the property from the server _description = (string?)GetValueFromCache(PropertyManager.Description, false); _descriptionInitialized = true; } } return _description; } set { CheckIfDisposed(); if (value != null && value.Length == 0) throw new ArgumentException(SR.EmptyStringParameter, nameof(value)); if (isBound) { // set the value on the directory entry SetProperty(PropertyManager.Description, value); } _description = value; } } public bool IsSingleValued { get { CheckIfDisposed(); if (isBound) { if (!_isSingleValuedInitialized) { // get the property from the abstract schema/ schema container // (for non-defunt classes this property is available in the abstract schema) if (!_isDefunctOnServer) { try { _isSingleValued = !_iadsProperty!.MultiValued; } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } } else { _isSingleValued = (bool)GetValueFromCache(PropertyManager.IsSingleValued, true)!; } _isSingleValuedInitialized = true; } } return _isSingleValued; } set { CheckIfDisposed(); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry _propertyEntry.Properties[PropertyManager.IsSingleValued].Value = value; } _isSingleValued = value; } } public bool IsIndexed { get { CheckIfDisposed(); return IsSetInSearchFlags(SearchFlags.IsIndexed); } set { CheckIfDisposed(); if (value) { SetBitInSearchFlags(SearchFlags.IsIndexed); } else { ResetBitInSearchFlags(SearchFlags.IsIndexed); } } } public bool IsIndexedOverContainer { get { CheckIfDisposed(); return IsSetInSearchFlags(SearchFlags.IsIndexedOverContainer); } set { CheckIfDisposed(); if (value) { SetBitInSearchFlags(SearchFlags.IsIndexedOverContainer); } else { ResetBitInSearchFlags(SearchFlags.IsIndexedOverContainer); } } } public bool IsInAnr { get { CheckIfDisposed(); return IsSetInSearchFlags(SearchFlags.IsInAnr); } set { CheckIfDisposed(); if (value) { SetBitInSearchFlags(SearchFlags.IsInAnr); } else { ResetBitInSearchFlags(SearchFlags.IsInAnr); } } } public bool IsOnTombstonedObject { get { CheckIfDisposed(); return IsSetInSearchFlags(SearchFlags.IsOnTombstonedObject); } set { CheckIfDisposed(); if (value) { SetBitInSearchFlags(SearchFlags.IsOnTombstonedObject); } else { ResetBitInSearchFlags(SearchFlags.IsOnTombstonedObject); } } } public bool IsTupleIndexed { get { CheckIfDisposed(); return IsSetInSearchFlags(SearchFlags.IsTupleIndexed); } set { CheckIfDisposed(); if (value) { SetBitInSearchFlags(SearchFlags.IsTupleIndexed); } else { ResetBitInSearchFlags(SearchFlags.IsTupleIndexed); } } } public bool IsInGlobalCatalog { get { CheckIfDisposed(); if (isBound) { if (!_isInGlobalCatalogInitialized) { // get the property from the server object? value = GetValueFromCache(PropertyManager.IsMemberOfPartialAttributeSet, false); _isInGlobalCatalog = (value != null) ? (bool)value : false; _isInGlobalCatalogInitialized = true; } } return _isInGlobalCatalog; } set { CheckIfDisposed(); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry _propertyEntry.Properties[PropertyManager.IsMemberOfPartialAttributeSet].Value = value; } _isInGlobalCatalog = value; } } public Nullable<int> RangeLower { get { CheckIfDisposed(); if (isBound) { if (!_rangeLowerInitialized) { // get the property from the server // if the property is not set then we will return null object? value = GetValueFromCache(PropertyManager.RangeLower, false); if (value == null) { _rangeLower = null; } else { _rangeLower = (int)value; } _rangeLowerInitialized = true; } } return _rangeLower; } set { CheckIfDisposed(); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry if (value == null) { if (_propertyEntry.Properties.Contains(PropertyManager.RangeLower)) { _propertyEntry.Properties[PropertyManager.RangeLower].Clear(); } } else { _propertyEntry.Properties[PropertyManager.RangeLower].Value = (int)value.Value; } } _rangeLower = value; } } public Nullable<int> RangeUpper { get { CheckIfDisposed(); if (isBound) { if (!_rangeUpperInitialized) { // get the property from the server // if the property is not set then we will return null object? value = GetValueFromCache(PropertyManager.RangeUpper, false); if (value == null) { _rangeUpper = null; } else { _rangeUpper = (int)value; } _rangeUpperInitialized = true; } } return _rangeUpper; } set { CheckIfDisposed(); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry if (value == null) { if (_propertyEntry.Properties.Contains(PropertyManager.RangeUpper)) { _propertyEntry.Properties[PropertyManager.RangeUpper].Clear(); } } else { _propertyEntry.Properties[PropertyManager.RangeUpper].Value = (int)value.Value; } } _rangeUpper = value; } } public bool IsDefunct { get { CheckIfDisposed(); // this is initialized for bound properties in the constructor return _isDefunct; } set { CheckIfDisposed(); if (isBound) { // set the value on the directory entry SetProperty(PropertyManager.IsDefunct, value); } _isDefunct = value; } } public ActiveDirectorySchemaProperty? Link { get { CheckIfDisposed(); if (isBound) { if (!_linkedPropertyInitialized) { object? value = GetValueFromCache(PropertyManager.LinkID, false); int tempLinkId = (value != null) ? (int)value : -1; if (tempLinkId != -1) { int linkIdToSearch = tempLinkId - 2 * (tempLinkId % 2) + 1; try { _schemaEntry ??= DirectoryEntryManager.GetDirectoryEntry(_context, WellKnownDN.SchemaNamingContext); string filter = "(&(" + PropertyManager.ObjectCategory + "=attributeSchema)" + "(" + PropertyManager.LinkID + "=" + linkIdToSearch + "))"; ReadOnlyActiveDirectorySchemaPropertyCollection linkedProperties = ActiveDirectorySchema.GetAllProperties(_context, _schemaEntry, filter); if (linkedProperties.Count != 1) { throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.LinkedPropertyNotFound, linkIdToSearch), typeof(ActiveDirectorySchemaProperty), null); } _linkedProperty = linkedProperties[0]; } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } } _linkedPropertyInitialized = true; } } return _linkedProperty; } } public Nullable<int> LinkId { get { CheckIfDisposed(); if (isBound) { if (!_linkIdInitialized) { object? value = GetValueFromCache(PropertyManager.LinkID, false); // if the property was not set we will return null if (value == null) { _linkId = null; } else { _linkId = (int)value; } _linkIdInitialized = true; } } return _linkId; } set { CheckIfDisposed(); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry if (value == null) { if (_propertyEntry.Properties.Contains(PropertyManager.LinkID)) { _propertyEntry.Properties[PropertyManager.LinkID].Clear(); } } else { _propertyEntry.Properties[PropertyManager.LinkID].Value = (int)value.Value; } } _linkId = value; } } public Guid SchemaGuid { get { CheckIfDisposed(); if (isBound) { // get the property from the server _schemaGuidBinaryForm ??= (byte[])GetValueFromCache(PropertyManager.SchemaIDGuid, true)!; } // we cache the byte array and create a new guid each time return new Guid(_schemaGuidBinaryForm!); } set { CheckIfDisposed(); if (isBound) { // set the value on the directory entry SetProperty(PropertyManager.SchemaIDGuid, (value.Equals(Guid.Empty)) ? null : value.ToByteArray()); } _schemaGuidBinaryForm = (value.Equals(Guid.Empty)) ? null : value.ToByteArray(); } } #endregion public properties #region private methods private void CheckIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } // // This method retrieves the value of a property (single valued) from the values // that were retrieved from the server. The "mustExist" parameter controls whether or // not an exception should be thrown if a value does not exist. If mustExist is true, this // will throw an exception is value does not exist. // private object? GetValueFromCache(string propertyName, bool mustExist) { object? value = null; // retrieve the properties from the server if necessary InitializePropertiesFromSchemaContainer(); Debug.Assert(_propertyValuesFromServer != null); ResultPropertyValueCollection? propertyValues = null; try { propertyValues = _propertyValuesFromServer.Properties[propertyName]; if ((propertyValues == null) || (propertyValues.Count < 1)) { if (mustExist) { throw new ActiveDirectoryOperationException(SR.Format(SR.PropertyNotFound, propertyName)); } } else { value = propertyValues[0]; } } catch (COMException e) { throw ExceptionHelper.GetExceptionFromCOMException(_context, e); } return value; } // // Just calls the static method GetPropertiesFromSchemaContainer with the correct context // private void InitializePropertiesFromSchemaContainer() { if (!_propertiesFromSchemaContainerInitialized) { _schemaEntry ??= DirectoryEntryManager.GetDirectoryEntry(_context, WellKnownDN.SchemaNamingContext); _propertyValuesFromServer = GetPropertiesFromSchemaContainer(_context, _schemaEntry, (_isDefunctOnServer) ? _commonName! : _ldapDisplayName, _isDefunctOnServer); _propertiesFromSchemaContainerInitialized = true; } } // // This method retrieves properties for this schema class from the schema container // on the server. For non-defunct classes only properties that are not available in the abstract // schema are retrieved. For defunct classes, all the properties are retrieved. // The retrieved values are stored in a class variable "propertyValuesFromServer" which is a // hashtable indexed on the property name. // internal static SearchResult GetPropertiesFromSchemaContainer(DirectoryContext context, DirectoryEntry schemaEntry, string name, bool isDefunctOnServer) { SearchResult? propertyValuesFromServer = null; // // The properties that are loaded from the schemaContainer for non-defunct classes: // DistinguishedName // CommonName // Syntax - AttributeSyntax, OMSyntax, OMObjectClass // Description // IsIndexed, IsIndexedOverContainer, IsInAnr, IsOnTombstonedObject, IsTupleIndexed - SearchFlags // IsInGlobalCatalog - IsMemberOfPartialAttributeSet // LinkId (Link) // SchemaGuid - SchemaIdGuid // RangeLower // RangeUpper // // For defunct class we also load teh remaining properties // LdapDisplayName // Oid // IsSingleValued // // build the filter StringBuilder str = new StringBuilder(15); str.Append("(&("); str.Append(PropertyManager.ObjectCategory); str.Append("=attributeSchema)"); str.Append('('); if (!isDefunctOnServer) { str.Append(PropertyManager.LdapDisplayName); } else { str.Append(PropertyManager.Cn); } str.Append('='); str.Append(Utils.GetEscapedFilterValue(name)); str.Append(')'); if (!isDefunctOnServer) { str.Append("(!("); } else { str.Append('('); } str.Append(PropertyManager.IsDefunct); if (!isDefunctOnServer) { str.Append("=TRUE)))"); } else { str.Append("=TRUE))"); } string[]? propertiesToLoad = null; if (!isDefunctOnServer) { propertiesToLoad = new string[12]; propertiesToLoad[0] = PropertyManager.DistinguishedName; propertiesToLoad[1] = PropertyManager.Cn; propertiesToLoad[2] = PropertyManager.AttributeSyntax; propertiesToLoad[3] = PropertyManager.OMSyntax; propertiesToLoad[4] = PropertyManager.OMObjectClass; propertiesToLoad[5] = PropertyManager.Description; propertiesToLoad[6] = PropertyManager.SearchFlags; propertiesToLoad[7] = PropertyManager.IsMemberOfPartialAttributeSet; propertiesToLoad[8] = PropertyManager.LinkID; propertiesToLoad[9] = PropertyManager.SchemaIDGuid; propertiesToLoad[10] = PropertyManager.RangeLower; propertiesToLoad[11] = PropertyManager.RangeUpper; } else { propertiesToLoad = new string[15]; propertiesToLoad[0] = PropertyManager.DistinguishedName; propertiesToLoad[1] = PropertyManager.Cn; propertiesToLoad[2] = PropertyManager.AttributeSyntax; propertiesToLoad[3] = PropertyManager.OMSyntax; propertiesToLoad[4] = PropertyManager.OMObjectClass; propertiesToLoad[5] = PropertyManager.Description; propertiesToLoad[6] = PropertyManager.SearchFlags; propertiesToLoad[7] = PropertyManager.IsMemberOfPartialAttributeSet; propertiesToLoad[8] = PropertyManager.LinkID; propertiesToLoad[9] = PropertyManager.SchemaIDGuid; propertiesToLoad[10] = PropertyManager.AttributeID; propertiesToLoad[11] = PropertyManager.IsSingleValued; propertiesToLoad[12] = PropertyManager.RangeLower; propertiesToLoad[13] = PropertyManager.RangeUpper; propertiesToLoad[14] = PropertyManager.LdapDisplayName; } // // Get all the values (don't need to use range retrieval as there are no multivalued attributes) // ADSearcher searcher = new ADSearcher(schemaEntry, str.ToString(), propertiesToLoad, SearchScope.OneLevel, false /* paged search */, false /* cache results */); try { propertyValuesFromServer = searcher.FindOne(); } catch (COMException e) { if (e.ErrorCode == unchecked((int)0x80072030)) { // object is not found since we cannot even find the container in which to search throw new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySchemaProperty), name); } else { throw ExceptionHelper.GetExceptionFromCOMException(context, e); } } if (propertyValuesFromServer == null) { throw new ActiveDirectoryObjectNotFoundException(SR.DSNotFound, typeof(ActiveDirectorySchemaProperty), name); } return propertyValuesFromServer; } internal DirectoryEntry GetSchemaPropertyDirectoryEntry() { if (_propertyEntry == null) { InitializePropertiesFromSchemaContainer(); _propertyEntry = DirectoryEntryManager.GetDirectoryEntry(_context, (string)GetValueFromCache(PropertyManager.DistinguishedName, true)!); } return _propertyEntry; } /// /// <summary> /// Initializes the search flags attribute value i.e. fetches it from /// the directory, if this object is bound. /// </summary> /// private void InitializeSearchFlags() { if (isBound) { if (!_searchFlagsInitialized) { object? value = GetValueFromCache(PropertyManager.SearchFlags, false); if (value != null) { _searchFlags = (SearchFlags)((int)value); } _searchFlagsInitialized = true; } } } private bool IsSetInSearchFlags(SearchFlags searchFlagBit) { InitializeSearchFlags(); return (((int)_searchFlags & (int)searchFlagBit) != 0); } private void SetBitInSearchFlags(SearchFlags searchFlagBit) { InitializeSearchFlags(); _searchFlags = (SearchFlags)((int)_searchFlags | (int)searchFlagBit); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry _propertyEntry.Properties[PropertyManager.SearchFlags].Value = (int)_searchFlags; } } private void ResetBitInSearchFlags(SearchFlags searchFlagBit) { InitializeSearchFlags(); _searchFlags = (SearchFlags)((int)_searchFlags & ~((int)searchFlagBit)); if (isBound) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); // set the value on the directory entry _propertyEntry.Properties[PropertyManager.SearchFlags].Value = (int)_searchFlags; } } private void SetProperty(string propertyName, object? value) { // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); if (value == null) { if (_propertyEntry.Properties.Contains(propertyName)) { _propertyEntry.Properties[propertyName].Clear(); } } else { _propertyEntry.Properties[propertyName].Value = value; } } private ActiveDirectorySyntax MapSyntax(string syntaxId, int oMID, OMObjectClass? oMObjectClass) { for (int i = 0; i < s_syntaxes.Length; i++) { if (s_syntaxes[i].Equals(new Syntax(syntaxId, oMID, oMObjectClass))) { return (ActiveDirectorySyntax)i; } } throw new ActiveDirectoryOperationException(SR.Format(SR.UnknownSyntax, _ldapDisplayName)); } private void SetSyntax(ActiveDirectorySyntax syntax) { if ((((int)syntax) < 0) || (((int)syntax) > (s_syntaxes.Length - 1))) { throw new InvalidEnumArgumentException(nameof(syntax), (int)syntax, typeof(ActiveDirectorySyntax)); } // get the distinguished name to construct the directory entry GetSchemaPropertyDirectoryEntry(); Debug.Assert(_propertyEntry != null); _propertyEntry.Properties[PropertyManager.AttributeSyntax].Value = s_syntaxes[(int)syntax].attributeSyntax; _propertyEntry.Properties[PropertyManager.OMSyntax].Value = s_syntaxes[(int)syntax].oMSyntax; OMObjectClass? oMObjectClass = s_syntaxes[(int)syntax].oMObjectClass; if (oMObjectClass != null) { _propertyEntry.Properties[PropertyManager.OMObjectClass].Value = oMObjectClass.Data; } } #endregion private methods } } |