1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 using System.Diagnostics;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.InteropServices;
12 using System.Security.Principal;
13 using System.Security.Permissions;
14 using System.Collections.Specialized;
15 using System.DirectoryServices;
17 using MACLPrinc = System.Security.Principal;
18 using System.Security.AccessControl;
19 using System.DirectoryServices.ActiveDirectory;
21 namespace System.DirectoryServices.AccountManagement
23 internal partial class ADStoreCtx : StoreCtx
25 protected DirectoryEntry ctxBase;
26 private const int mappingIndex = 0;
28 private object _ctxBaseLock = new object(); // when mutating ctxBase
30 private bool _ownCtxBase; // if true, we "own" ctxBase and must Dispose of it when we're done
32 private bool _disposed = false;
34 protected internal NetCred Credentials { get { return this.credentials; } }
35 protected NetCred credentials = null;
37 protected internal AuthenticationTypes AuthTypes { get { return this.authTypes; } }
38 protected AuthenticationTypes authTypes;
40 protected ContextOptions contextOptions;
42 protected internal virtual void InitializeNewDirectoryOptions(DirectoryEntry newDeChild)
47 // Static constructor: used for initializing static tables
52 // Load the filterPropertiesTable
54 LoadFilterMappingTable(mappingIndex, s_filterPropertiesTableRaw);
55 LoadPropertyMappingTable(mappingIndex, s_propertyMappingTableRaw);
58 protected virtual int MappingTableIndex
66 protected static void LoadFilterMappingTable(int mappingIndex, object[,] rawFilterPropertiesTable)
68 if (null == s_filterPropertiesTable)
69 s_filterPropertiesTable = new Hashtable();
71 Hashtable mappingTable = new Hashtable();
73 for (int i = 0; i < rawFilterPropertiesTable.GetLength(0); i++)
75 Type qbeType = rawFilterPropertiesTable[i, 0] as Type;
76 string adPropertyName = rawFilterPropertiesTable[i, 1] as string;
77 FilterConverterDelegate f = rawFilterPropertiesTable[i, 2] as FilterConverterDelegate;
79 Debug.Assert(qbeType != null);
80 Debug.Assert(f != null);
82 // There should only be one entry per QBE type
83 Debug.Assert(mappingTable[qbeType] == null);
85 FilterPropertyTableEntry entry = new FilterPropertyTableEntry();
86 entry.suggestedADPropertyName = adPropertyName;
89 mappingTable[qbeType] = entry;
92 s_filterPropertiesTable.Add(mappingIndex, mappingTable);
95 protected static void LoadPropertyMappingTable(int mappingIndex, object[,] rawPropertyMappingTable)
98 // Load the propertyMappingTableByProperty and propertyMappingTableByLDAP tables
100 if (null == s_propertyMappingTableByProperty)
101 s_propertyMappingTableByProperty = new Hashtable();
103 if (null == s_propertyMappingTableByLDAP)
104 s_propertyMappingTableByLDAP = new Hashtable();
106 if (null == s_propertyMappingTableByPropertyFull)
107 s_propertyMappingTableByPropertyFull = new Hashtable();
109 if (null == TypeToLdapPropListMap)
110 TypeToLdapPropListMap = new Dictionary<int, Dictionary<Type, StringCollection>>();
112 Hashtable mappingTableByProperty = new Hashtable();
113 Hashtable mappingTableByLDAP = new Hashtable();
114 Hashtable mappingTableByPropertyFull = new Hashtable();
116 Dictionary<string, string[]> propertyNameToLdapAttr = new Dictionary<string, string[]>();
118 Dictionary<Type, StringCollection> TypeToLdapDict = new Dictionary<Type, StringCollection>();
120 for (int i = 0; i < s_propertyMappingTableRaw.GetLength(0); i++)
122 string propertyName = rawPropertyMappingTable[i, 0] as string;
123 string ldapAttribute = rawPropertyMappingTable[i, 1] as string;
124 FromLdapConverterDelegate fromLdap = rawPropertyMappingTable[i, 2] as FromLdapConverterDelegate;
125 ToLdapConverterDelegate toLdap = rawPropertyMappingTable[i, 3] as ToLdapConverterDelegate;
127 Debug.Assert(propertyName != null);
128 Debug.Assert((ldapAttribute != null && fromLdap != null) || (fromLdap == null));
129 //Debug.Assert(toLdap != null);
131 // Build the table entry. The same entry will be used in both tables.
132 // Once constructed, the table entries are treated as read-only, so there's
133 // no danger in sharing the entries between tables.
134 PropertyMappingTableEntry propertyEntry = new PropertyMappingTableEntry();
135 propertyEntry.propertyName = propertyName;
136 propertyEntry.suggestedADPropertyName = ldapAttribute;
137 propertyEntry.ldapToPapiConverter = fromLdap;
138 propertyEntry.papiToLdapConverter = toLdap;
140 // Build a mapping table from PAPI propertyname to ldapAttribute that we can use below
141 // to build a list of ldap attributes for each object type.
142 if (null != ldapAttribute)
144 if (propertyNameToLdapAttr.ContainsKey(propertyName))
146 string[] props = new string[propertyNameToLdapAttr[propertyName].Length + 1];
147 propertyNameToLdapAttr[propertyName].CopyTo(props, 0);
148 props[propertyNameToLdapAttr[propertyName].Length] = ldapAttribute;
149 propertyNameToLdapAttr[propertyName] = props;
152 propertyNameToLdapAttr.Add(propertyName, new string[] { ldapAttribute });
155 // propertyMappingTableByProperty
156 // If toLdap is null, there's no PAPI->LDAP mapping for this property
157 // (it's probably read-only, e.g., "lastLogon").
160 if (mappingTableByProperty[propertyName] == null)
161 mappingTableByProperty[propertyName] = new ArrayList();
163 ((ArrayList)mappingTableByProperty[propertyName]).Add(propertyEntry);
166 if (mappingTableByPropertyFull[propertyName] == null)
167 mappingTableByPropertyFull[propertyName] = new ArrayList();
169 ((ArrayList)mappingTableByPropertyFull[propertyName]).Add(propertyEntry);
171 // mappingTableByLDAP
172 // If fromLdap is null, there's no direct LDAP->PAPI mapping for this property.
173 // It's probably a property that requires custom handling, such as IdentityClaim.
174 if (fromLdap != null)
176 string ldapAttributeLower = ldapAttribute.ToLower(CultureInfo.InvariantCulture);
178 if (mappingTableByLDAP[ldapAttributeLower] == null)
179 mappingTableByLDAP[ldapAttributeLower] = new ArrayList();
181 ((ArrayList)mappingTableByLDAP[ldapAttributeLower]).Add(propertyEntry);
185 s_propertyMappingTableByProperty.Add(mappingIndex, mappingTableByProperty);
186 s_propertyMappingTableByLDAP.Add(mappingIndex, mappingTableByLDAP);
187 s_propertyMappingTableByPropertyFull.Add(mappingIndex, mappingTableByPropertyFull);
189 // Build a table of Type mapped to a collection of all ldap attributes for that type.
190 // This table will be used to load the objects when searching.
192 StringCollection principalPropList = new StringCollection();
193 StringCollection authPrincipalPropList = new StringCollection();
194 StringCollection userPrincipalPropList = new StringCollection();
195 StringCollection computerPrincipalPropList = new StringCollection();
196 StringCollection groupPrincipalPropList = new StringCollection();
198 foreach (string prop in principalProperties)
201 if (propertyNameToLdapAttr.TryGetValue(prop, out attr))
203 foreach (string plist in attr)
205 principalPropList.Add(plist);
206 authPrincipalPropList.Add(plist);
207 userPrincipalPropList.Add(plist);
208 computerPrincipalPropList.Add(plist);
209 groupPrincipalPropList.Add(plist);
214 foreach (string prop in authenticablePrincipalProperties)
217 if (propertyNameToLdapAttr.TryGetValue(prop, out attr))
219 foreach (string plist in attr)
221 authPrincipalPropList.Add(plist);
222 userPrincipalPropList.Add(plist);
223 computerPrincipalPropList.Add(plist);
228 foreach (string prop in groupProperties)
231 if (propertyNameToLdapAttr.TryGetValue(prop, out attr))
233 foreach (string plist in attr)
235 groupPrincipalPropList.Add(plist);
240 foreach (string prop in userProperties)
243 if (propertyNameToLdapAttr.TryGetValue(prop, out attr))
245 foreach (string plist in attr)
247 userPrincipalPropList.Add(plist);
252 foreach (string prop in computerProperties)
255 if (propertyNameToLdapAttr.TryGetValue(prop, out attr))
257 foreach (string plist in attr)
259 computerPrincipalPropList.Add(plist);
264 principalPropList.Add("objectClass");
265 authPrincipalPropList.Add("objectClass");
266 userPrincipalPropList.Add("objectClass");
267 computerPrincipalPropList.Add("objectClass");
268 groupPrincipalPropList.Add("objectClass");
270 TypeToLdapDict.Add(typeof(Principal), principalPropList);
271 TypeToLdapDict.Add(typeof(GroupPrincipal), groupPrincipalPropList);
272 TypeToLdapDict.Add(typeof(AuthenticablePrincipal), authPrincipalPropList);
273 TypeToLdapDict.Add(typeof(UserPrincipal), userPrincipalPropList);
274 TypeToLdapDict.Add(typeof(ComputerPrincipal), computerPrincipalPropList);
276 TypeToLdapPropListMap.Add(mappingIndex, TypeToLdapDict);
283 // Throws ArgumentException if base is not a container class (as indicated by an empty possibleInferiors
284 // attribute in the corresponding schema class definition)
285 public ADStoreCtx(DirectoryEntry ctxBase, bool ownCtxBase, string username, string password, ContextOptions options)
287 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Constructing ADStoreCtx for {0}", ctxBase.Path);
289 Debug.Assert(ctxBase != null);
291 // This will also detect if the server is down or nonexistent
292 if (!IsContainer(ctxBase))
293 throw new InvalidOperationException(SR.ADStoreCtxMustBeContainer);
295 this.ctxBase = ctxBase;
296 _ownCtxBase = ownCtxBase;
298 if (username != null && password != null)
299 this.credentials = new NetCred(username, password);
301 this.contextOptions = options;
302 this.authTypes = SDSUtils.MapOptionsToAuthTypes(options);
305 protected bool IsContainer(DirectoryEntry de)
307 //NOTE: Invoking de.SchemaEntry creates a new DirectoryEntry object, which is not disposed by de.
308 using (DirectoryEntry schemaDE = de.SchemaEntry)
310 if (schemaDE.Properties["possibleInferiors"].Count == 0)
312 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsContainer: not a container ({0})", schemaDE.Path);
320 // IDisposable implementation
323 public override void Dispose()
329 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Dispose: disposing, ownCtxBase={0}", _ownCtxBase);
344 // StoreCtx information
347 // Retrieves the Path (ADsPath) of the object used as the base of the StoreCtx
348 internal override string BasePath
352 Debug.Assert(this.ctxBase != null);
353 return this.ctxBase.Path;
361 // Used to perform the specified operation on the Principal.
363 // Insert() and Update() must check to make sure no properties not supported by this StoreCtx
364 // have been set, prior to persisting the Principal.
365 internal override void Insert(Principal p)
369 Debug.Assert(p.unpersisted == true);
370 Debug.Assert(p.fakePrincipal == false);
372 // Insert the principal into the store
373 SDSUtils.InsertPrincipal(
376 new SDSUtils.GroupMembershipUpdater(UpdateGroupMembership),
382 // Load in all the initial values from the store
383 //((DirectoryEntry)p.UnderlyingObject).RefreshCache();
384 LoadDirectoryEntryAttributes((DirectoryEntry)p.UnderlyingObject);
386 // If they set p.Enabled == true, enable the principal
387 EnablePrincipalIfNecessary(p);
389 // If they set CannotChangePassword then we need to set it here after the object is already created.
390 SetPasswordSecurityifNeccessary(p);
392 // Load in the StoreKey
393 Debug.Assert(p.Key == null); // since it was previously unpersisted
395 Debug.Assert(p.UnderlyingObject != null); // since we just persisted it
396 Debug.Assert(p.UnderlyingObject is DirectoryEntry);
398 ADStoreKey key = new ADStoreKey(((DirectoryEntry)p.UnderlyingObject).Guid);
401 // Reset the change tracking
402 p.ResetAllChangeStatus();
404 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert: new GUID is ", ((DirectoryEntry)p.UnderlyingObject).Guid);
406 catch (PrincipalExistsException)
408 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, object already exists");
411 catch (System.SystemException e)
415 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Insert, Save Failed (attempting to delete) Exception {0} ", e.Message);
416 if (null != p.UnderlyingObject)
418 SDSUtils.DeleteDirectoryEntry((DirectoryEntry)p.UnderlyingObject);
419 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, object deleted");
423 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Insert, No object was created nothing to delete");
426 catch (System.Runtime.InteropServices.COMException deleteFail)
428 // The delete failed. Just continue we will throw the original exception below.
429 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Insert, Deletion Failed {0} ", deleteFail.Message);
432 if (e is System.Runtime.InteropServices.COMException)
433 throw ExceptionHelper.GetExceptionFromCOMException((System.Runtime.InteropServices.COMException)e);
439 internal override bool AccessCheck(Principal p, PrincipalAccessMask targetPermission)
441 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "AccessCheck " + targetPermission.ToString());
443 switch (targetPermission)
445 case PrincipalAccessMask.ChangePassword:
447 return CannotChangePwdFromLdapConverter((DirectoryEntry)p.GetUnderlyingObject());
451 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "Invalid targetPermission in AccessCheck");
460 /// If The enabled property was set on the principal then perform actions
461 /// necessary on the principal to set the enabled status to match
464 /// <param name="p"></param>
465 private void EnablePrincipalIfNecessary(Principal p)
467 if (p.GetChangeStatusForProperty(PropertyNames.AuthenticablePrincipalEnabled))
469 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "EnablePrincipalIfNecessary: enabling principal");
471 Debug.Assert(p is AuthenticablePrincipal);
473 bool enable = (bool)p.GetValueForProperty(PropertyNames.AuthenticablePrincipalEnabled);
475 SetAuthPrincipalEnableStatus((AuthenticablePrincipal)p, enable);
479 private void SetPasswordSecurityifNeccessary(Principal p)
481 if (p.GetChangeStatusForProperty(PropertyNames.PwdInfoCannotChangePassword))
483 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "EnablePrincipalIfNecessary: enabling principal");
485 Debug.Assert(p is AuthenticablePrincipal);
487 SetCannotChangePasswordStatus((AuthenticablePrincipal)p, (bool)p.GetValueForProperty(PropertyNames.PwdInfoCannotChangePassword), true);
491 private static void SetCannotChangePasswordStatus(Principal ap, bool userCannotChangePassword, bool commitChanges)
493 Debug.Assert(ap is AuthenticablePrincipal);
494 Debug.Assert(ap.GetUnderlyingObject() is DirectoryEntry);
496 DirectoryEntry de = (DirectoryEntry)ap.GetUnderlyingObject();
497 // retrieving ObjectSecurity after
498 // previously modifying the ACL will return null unless we force a cache refresh. We have to do this always,
499 // even before we call ObjectSecurity to see if it would return null, because once ObjectSecurity returns null the
500 // first time, it'll keep returning null even if we refresh the cache.
501 if (!de.Properties.Contains("nTSecurityDescriptor"))
502 de.RefreshCache(new string[] { "nTSecurityDescriptor" });
503 ActiveDirectorySecurity adsSecurity = de.ObjectSecurity;
508 bool allowWorldFound;
510 // Scan the existing ACL to determine its current state
512 ScanACLForChangePasswordRight(adsSecurity, out denySelfFound, out denyWorldFound, out allowSelfFound, out allowWorldFound);
514 // Build the ACEs that we'll use
515 ActiveDirectoryAccessRule denySelfACE = new ExtendedRightAccessRule(
516 new MACLPrinc.SecurityIdentifier(SelfSddl),
517 AccessControlType.Deny,
518 s_changePasswordGuid);
520 ActiveDirectoryAccessRule denyWorldAce = new ExtendedRightAccessRule(
521 new MACLPrinc.SecurityIdentifier(WorldSddl),
522 AccessControlType.Deny,
523 s_changePasswordGuid);
525 ActiveDirectoryAccessRule allowSelfACE = new ExtendedRightAccessRule(
526 new MACLPrinc.SecurityIdentifier(SelfSddl),
527 AccessControlType.Allow,
528 s_changePasswordGuid);
530 ActiveDirectoryAccessRule allowWorldAce = new ExtendedRightAccessRule(
531 new MACLPrinc.SecurityIdentifier(WorldSddl),
532 AccessControlType.Allow,
533 s_changePasswordGuid);
535 // Based on the current state of the ACL and the userCannotChangePassword status, perform the necessary modifications,
537 if (userCannotChangePassword)
539 // If we want to make it so the user cannot change their password, we need to remove the ALLOW ACEs
540 // (if they exist) and add the necessary explicit DENY ACEs if they don't already exist.
544 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add deny self");
545 adsSecurity.AddAccessRule(denySelfACE);
550 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add deny world");
551 adsSecurity.AddAccessRule(denyWorldAce);
556 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove allow self");
557 adsSecurity.RemoveAccessRuleSpecific(allowSelfACE);
562 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove allow world");
563 adsSecurity.RemoveAccessRuleSpecific(allowWorldAce);
568 // If we want to make to give the user back the right to change their password, we need to remove
569 // the explicit DENY ACEs if they exist. We'll also add in explicit ALLOW ACEs.
573 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove deny self");
574 adsSecurity.RemoveAccessRuleSpecific(denySelfACE);
579 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: remove deny world");
580 adsSecurity.RemoveAccessRuleSpecific(denyWorldAce);
585 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add allow self");
586 adsSecurity.AddAccessRule(allowSelfACE);
589 if (!allowWorldFound)
591 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CannotChangePwdToLdapConverter: add allow world");
592 adsSecurity.AddAccessRule(allowWorldAce);
600 /// Read the Account Control From the Directory entry. If the control is read then set or
601 /// clear bit 0x2 corresponding to the enable parameter
603 /// <param name="ap">Principal to modify</param>
604 /// <param name="enable">New state of the enable bit</param>
607 protected virtual void SetAuthPrincipalEnableStatus(AuthenticablePrincipal ap, bool enable)
611 Debug.Assert(ap.fakePrincipal == false);
615 DirectoryEntry de = (DirectoryEntry)ap.UnderlyingObject;
617 if (de.Properties["userAccountControl"].Count > 0)
619 Debug.Assert(de.Properties["userAccountControl"].Count == 1);
621 uacValue = (int)de.Properties["userAccountControl"][0];
625 // Since we loaded the properties, we should have it. Perhaps we don't have access
626 // to it. In that case, we don't want to blindly overwrite whatever other bits might be there.
627 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: can't read userAccountControl");
629 throw new PrincipalOperationException(
630 SR.ADStoreCtxUnableToReadExistingAccountControlFlagsToEnable);
633 if (enable && ((uacValue & 0x2) != 0))
635 // It's currently disabled, and we need to enable it
636 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: Enabling (old uac={0})", uacValue);
638 Utils.ClearBit(ref uacValue, 0x2); // UF_ACCOUNTDISABLE
640 WriteAttribute(ap, "userAccountControl", uacValue);
642 else if (!enable && ((uacValue & 0x2) == 0))
644 // It's current enabled, and we need to disable it
645 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "SetAuthPrincipalEnableStatus: Disabling (old uac={0})", uacValue);
647 Utils.SetBit(ref uacValue, 0x2); // UF_ACCOUNTDISABLE
649 WriteAttribute(ap, "userAccountControl", uacValue);
652 catch (System.Runtime.InteropServices.COMException e)
654 throw ExceptionHelper.GetExceptionFromCOMException(e);
658 /// Apply all changed properties on the principal to the Directory Entry.
659 /// Reset the changed status on all the properties
661 /// <param name="p">Principal to update</param>
662 internal override void Update(Principal p)
666 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Update");
668 Debug.Assert(p.fakePrincipal == false);
669 Debug.Assert(p.unpersisted == false);
670 Debug.Assert(p.UnderlyingObject != null);
671 Debug.Assert(p.UnderlyingObject is DirectoryEntry);
673 // Commit the properties
674 SDSUtils.ApplyChangesToDirectory(
677 new SDSUtils.GroupMembershipUpdater(UpdateGroupMembership),
682 // Reset the change tracking
683 p.ResetAllChangeStatus();
685 catch (System.Runtime.InteropServices.COMException e)
687 throw ExceptionHelper.GetExceptionFromCOMException(e);
692 /// Delete the directory entry that corresponds to the principal
694 /// <param name="p">Principal to delete</param>
695 internal override void Delete(Principal p)
699 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Delete");
701 Debug.Assert(p.fakePrincipal == false);
703 // Principal.Delete() shouldn't be calling us on an unpersisted Principal.
704 Debug.Assert(p.unpersisted == false);
705 Debug.Assert(p.UnderlyingObject != null);
707 Debug.Assert(p.UnderlyingObject is DirectoryEntry);
708 SDSUtils.DeleteDirectoryEntry((DirectoryEntry)p.UnderlyingObject);
710 catch (System.Runtime.InteropServices.COMException e)
712 throw ExceptionHelper.GetExceptionFromCOMException(e);
716 internal override void Move(StoreCtx originalStore, Principal p)
718 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Move");
720 Debug.Assert(p != null);
721 Debug.Assert(originalStore != null);
722 Debug.Assert(originalStore is ADStoreCtx);
725 string rdnPrefix = p.ExtensionHelper.RdnPrefix;
726 string baseObjectRdnPrefix = null;
727 Type principalType = p.GetType();
729 if (null == rdnPrefix)
731 throw new InvalidOperationException(SR.ExtensionInvalidClassAttributes);
734 if (p.GetChangeStatusForProperty(PropertyNames.PrincipalName))
736 name = rdnPrefix + "=" + (string)p.GetValueForProperty(PropertyNames.PrincipalName);
738 // If the principal class is derived from Group, Computer or User then we need to see
739 // if the class has an RdnPrefix set that differs from the base class prefix. If so then we need
740 // to modify that attribute when if changed the name during the move.
742 if (principalType.IsSubclassOf(typeof(GroupPrincipal)) ||
743 principalType.IsSubclassOf(typeof(UserPrincipal)) ||
744 principalType.IsSubclassOf(typeof(ComputerPrincipal)))
746 DirectoryRdnPrefixAttribute[] MyAttribute =
747 (DirectoryRdnPrefixAttribute[])Attribute.GetCustomAttributes(principalType.BaseType, typeof(DirectoryRdnPrefixAttribute), false);
749 if (MyAttribute == null)
750 throw new InvalidOperationException(SR.ExtensionInvalidClassAttributes);
752 string defaultRdn = null;
754 // Search for the rdn prefix. This will use either the prefix that has a context type
755 // that matches the principals context or the first rdnPrefix that has a null context type
756 for (int i = 0; i < MyAttribute.Length; i++)
758 if ((MyAttribute[i].Context == null && null == defaultRdn) ||
759 (p.ContextType == MyAttribute[i].Context))
761 defaultRdn = MyAttribute[i].RdnPrefix;
765 // If the base objects RDN prefix is not the same as the dervied class then we need to set both
766 if (defaultRdn != rdnPrefix)
768 baseObjectRdnPrefix = defaultRdn;
773 SDSUtils.MoveDirectoryEntry((DirectoryEntry)p.GetUnderlyingObject(),
777 p.LoadValueIntoProperty(PropertyNames.PrincipalName, p.GetValueForProperty(PropertyNames.PrincipalName));
779 if (null != baseObjectRdnPrefix)
781 ((DirectoryEntry)p.GetUnderlyingObject()).Properties[baseObjectRdnPrefix].Value = (string)p.GetValueForProperty(PropertyNames.PrincipalName);
786 // Special operations: the Principal classes delegate their implementation of many of the
787 // special methods to their underlying StoreCtx
790 // methods for manipulating accounts
793 /// This method sets the default user account control bits for the new principal
794 /// being created in this account store.
796 /// <param name="p"> Principal to set the user account control bits for </param>
797 internal override void InitializeUserAccountControl(AuthenticablePrincipal p)
799 Debug.Assert(p != null);
800 Debug.Assert(p.fakePrincipal == false);
801 Debug.Assert(p.unpersisted == true); // should only ever be called for new principals
803 // set the userAccountControl bits on the underlying directory entry
804 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
805 Debug.Assert(de != null);
806 Type principalType = p.GetType();
808 if ((principalType == typeof(ComputerPrincipal)) || (principalType.IsSubclassOf(typeof(ComputerPrincipal))))
810 de.Properties["userAccountControl"].Value = SDSUtils.AD_DefaultUAC_Machine;
812 else if ((principalType == typeof(UserPrincipal)) || (principalType.IsSubclassOf(typeof(UserPrincipal))))
814 de.Properties["userAccountControl"].Value = SDSUtils.AD_DefaultUAC;
819 /// Determine if principal account is locked.
820 /// First read User-Account-control-computed from the DE. On Uplevel platforms this computed attribute will exist and we can
821 /// just check bit 0x0010. On DL platforms this attribute does not exist so we must read lockoutTime and return locked if
822 /// this is greater than 0
824 /// <param name="p">Principal to check status</param>
825 /// <returns>true is account is locked, false if not</returns>
826 internal override bool IsLockedOut(AuthenticablePrincipal p)
830 Debug.Assert(p.fakePrincipal == false);
832 Debug.Assert(p.unpersisted == false);
834 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
835 Debug.Assert(de != null);
837 de.RefreshCache(new string[] { "msDS-User-Account-Control-Computed", "lockoutTime" });
839 if (de.Properties["msDS-User-Account-Control-Computed"].Count > 0)
841 // Uplevel platform --- the DC will compute it for us
842 Debug.Assert(de.Properties["msDS-User-Account-Control-Computed"].Count == 1);
843 int uacComputed = (int)de.Properties["msDS-User-Account-Control-Computed"][0];
845 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsLockedOut: using computed uac={0}", uacComputed);
847 return ((uacComputed & 0x0010) != 0); // UF_LOCKOUT
851 // Downlevel platform --- we have to compute it
852 bool isLockedOut = false;
854 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsLockedOut: downlevel");
856 if (de.Properties["lockoutTime"].Count > 0)
858 ulong lockoutTime = (ulong)ADUtils.LargeIntToInt64((UnsafeNativeMethods.IADsLargeInteger)de.Properties["lockoutTime"][0]);
860 if (lockoutTime != 0)
862 ulong lockoutDuration = this.LockoutDuration;
864 GlobalDebug.WriteLineIf(GlobalDebug.Info,
866 "IsLockedOut: lockoutTime={0}, lockoutDuration={1}",
870 if ((lockoutDuration + lockoutTime) > ((ulong)ADUtils.DateTimeToADFileTime(DateTime.UtcNow)))
878 catch (System.Runtime.InteropServices.COMException e)
880 throw ExceptionHelper.GetExceptionFromCOMException(e);
885 /// Unlock account by setting LockoutTime to 0
887 /// <param name="p">Principal to unlock</param>
888 internal override void UnlockAccount(AuthenticablePrincipal p)
890 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "UnlockAccount");
892 Debug.Assert(p.fakePrincipal == false);
894 WriteAttribute(p, "lockoutTime", 0);
897 // methods for manipulating passwords
899 /// Set the password on the principal. This function requires administrator privileges
901 /// <param name="p">Principal to modify</param>
902 /// <param name="newPassword">New password</param>
903 internal override void SetPassword(AuthenticablePrincipal p, string newPassword)
905 Debug.Assert(p.fakePrincipal == false);
907 Debug.Assert(p != null);
908 Debug.Assert(newPassword != null); // but it could be an empty string
910 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
911 Debug.Assert(de != null);
913 SDSUtils.SetPassword(de, newPassword);
917 /// Change the password on the principal
919 /// <param name="p">Principal to modify</param>
920 /// <param name="oldPassword">Current password</param>
921 /// <param name="newPassword">New password</param>
922 internal override void ChangePassword(AuthenticablePrincipal p, string oldPassword, string newPassword)
924 Debug.Assert(p.fakePrincipal == false);
926 // Shouldn't be being called if this is the case
927 Debug.Assert(p.unpersisted == false);
929 Debug.Assert(p != null);
930 Debug.Assert(newPassword != null); // but it could be an empty string
931 Debug.Assert(oldPassword != null); // but it could be an empty string
933 if ((p.GetType() == typeof(ComputerPrincipal)) || (p.GetType().IsSubclassOf(typeof(ComputerPrincipal))))
935 GlobalDebug.WriteLineIf(GlobalDebug.Error, "ADStoreCtx", "ChangePassword: computer acct, can't change password.");
936 throw new NotSupportedException(SR.ADStoreCtxNoComputerPasswordChange);
939 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
940 Debug.Assert(de != null);
942 SDSUtils.ChangePassword(de, oldPassword, newPassword);
945 /// Expire password by setting pwdLastSet to 0
947 /// <param name="p"></param>
948 internal override void ExpirePassword(AuthenticablePrincipal p)
950 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExpirePassword");
952 Debug.Assert(p.fakePrincipal == false);
954 WriteAttribute(p, "pwdLastSet", 0);
958 /// Unexpire password by setting pwdLastSet to -1
960 /// <param name="p"></param>
961 internal override void UnexpirePassword(AuthenticablePrincipal p)
963 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "UnexpirePassword");
965 Debug.Assert(p.fakePrincipal == false);
967 WriteAttribute(p, "pwdLastSet", -1);
971 /// Set value for attribute on the passed principal. This is only valid for integer attribute types
973 /// <param name="p"></param>
974 /// <param name="attribute"></param>
975 /// <param name="value"></param>
976 protected void WriteAttribute(Principal p, string attribute, int value)
980 Debug.Assert(p != null);
982 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
984 SDSUtils.WriteAttribute(de.Path, attribute, value, this.credentials, this.authTypes);
987 protected void WriteAttribute<T>(Principal p, string attribute, T value)
989 Debug.Assert(p != null);
991 DirectoryEntry de = (DirectoryEntry)p.UnderlyingObject;
993 SDSUtils.WriteAttribute<T>(de.Path, attribute, value, this.credentials, this.authTypes);
996 // the various FindBy* methods
997 internal override ResultSet FindByLockoutTime(
998 DateTime dt, MatchType matchType, Type principalType)
1000 return FindByDate(principalType, new string[] { "lockoutTime" }, matchType, dt);
1003 internal override ResultSet FindByLogonTime(
1004 DateTime dt, MatchType matchType, Type principalType)
1006 return FindByDate(principalType, new string[] { "lastLogon", "lastLogonTimestamp" }, matchType, dt);
1009 internal override ResultSet FindByPasswordSetTime(
1010 DateTime dt, MatchType matchType, Type principalType)
1012 return FindByDate(principalType, new string[] { "pwdLastSet" }, matchType, dt);
1015 internal override ResultSet FindByBadPasswordAttempt(
1016 DateTime dt, MatchType matchType, Type principalType)
1018 return FindByDate(principalType, new string[] { "badPasswordTime" }, matchType, dt);
1021 internal override ResultSet FindByExpirationTime(
1022 DateTime dt, MatchType matchType, Type principalType)
1024 return FindByDate(principalType, new string[] { "accountExpires" }, matchType, dt);
1027 private ResultSet FindByDate(Type subtype, string[] ldapAttributes, MatchType matchType, DateTime value)
1029 Debug.Assert(ldapAttributes != null);
1030 Debug.Assert(ldapAttributes.Length > 0);
1031 Debug.Assert(subtype == typeof(Principal) || subtype.IsSubclassOf(typeof(Principal)));
1032 DirectorySearcher ds = new DirectorySearcher(this.ctxBase);
1036 // Pick some reasonable default values
1038 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds
1040 // We don't need any attributes returned, since we're just going to get a DirectoryEntry
1041 // for the result. Per RFC 2251, OID 1.1 == no attributes.
1042 BuildPropertySet(subtype, ds.PropertiesToLoad);
1044 // Build the LDAP filter
1045 string ldapValue = ADUtils.DateTimeToADString(value);
1046 StringBuilder ldapFilter = new StringBuilder();
1048 ldapFilter.Append(GetObjectClassPortion(subtype));
1049 ldapFilter.Append("(|");
1051 foreach (string ldapAttribute in ldapAttributes)
1053 ldapFilter.Append("(");
1057 case MatchType.Equals:
1058 ldapFilter.Append(ldapAttribute);
1059 ldapFilter.Append("=");
1060 ldapFilter.Append(ldapValue);
1063 case MatchType.NotEquals:
1064 ldapFilter.Append("!(");
1065 ldapFilter.Append(ldapAttribute);
1066 ldapFilter.Append("=");
1067 ldapFilter.Append(ldapValue);
1068 ldapFilter.Append(")");
1071 case MatchType.GreaterThanOrEquals:
1072 ldapFilter.Append(ldapAttribute);
1073 ldapFilter.Append(">=");
1074 ldapFilter.Append(ldapValue);
1077 case MatchType.LessThanOrEquals:
1078 ldapFilter.Append(ldapAttribute);
1079 ldapFilter.Append("<=");
1080 ldapFilter.Append(ldapValue);
1083 case MatchType.GreaterThan:
1084 ldapFilter.Append("&");
1086 // Greater-than-or-equals (or less-than-or-equals))
1087 ldapFilter.Append("(");
1088 ldapFilter.Append(ldapAttribute);
1089 ldapFilter.Append(matchType == MatchType.GreaterThan ? ">=" : "<=");
1090 ldapFilter.Append(ldapValue);
1091 ldapFilter.Append(")");
1094 ldapFilter.Append("(!(");
1095 ldapFilter.Append(ldapAttribute);
1096 ldapFilter.Append("=");
1097 ldapFilter.Append(ldapValue);
1098 ldapFilter.Append("))");
1100 // And exists (need to include because of tristate LDAP logic)
1101 ldapFilter.Append("(");
1102 ldapFilter.Append(ldapAttribute);
1103 ldapFilter.Append("=*)");
1106 case MatchType.LessThan:
1107 goto case MatchType.GreaterThan;
1110 Debug.Fail("ADStoreCtx.FindByDate: fell off end looking for " + matchType.ToString());
1114 ldapFilter.Append(")");
1117 ldapFilter.Append("))");
1119 ds.Filter = ldapFilter.ToString();
1120 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "FindByDate: using LDAP filter {0}", ds.Filter);
1122 // Perform the search
1123 SearchResultCollection src = ds.FindAll();
1124 Debug.Assert(src != null);
1126 // Create a ResultSet for the search results
1127 ADEntriesSet resultSet = new ADEntriesSet(src, this);
1131 catch (System.Runtime.InteropServices.COMException e)
1133 throw ExceptionHelper.GetExceptionFromCOMException(e);
1141 // Get groups of which p is a direct member
1143 // 1. search for group with same group id as principals primary group ID.
1145 // 2. use enumeration to expand the users group membership
1146 // ASQ will not work because we cannot correctly generate referrals if one of the users
1147 // groups if from another domain in the forest.
1148 internal override ResultSet GetGroupsMemberOf(Principal p)
1150 // Enforced by the methods that call us
1151 Debug.Assert(p.unpersisted == false);
1153 DirectoryEntry gcPrincipalDe = null;
1154 DirectorySearcher memberOfSearcher = null;
1155 ADDNConstraintLinkedAttrSet.ResultValidator resultValidator = null;
1159 if (p.fakePrincipal)
1161 // If p is a fake principal, this will find the representation of p in the store
1162 // (namely, a FPO), and return the groups of which that FPO is a member
1163 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: fake principal");
1164 return GetGroupsMemberOf(p, this);
1167 Debug.Assert(p.UnderlyingObject != null);
1169 string primaryGroupDN = null;
1170 ResultSet resultSet = null;
1171 bool useASQ = false;
1172 List<DirectoryEntry> roots = new List<DirectoryEntry>(1);
1173 DirectorySearcher[] searchers = null;
1174 IEnumerable[] enumerators = null;
1176 DirectoryEntry principalDE = (DirectoryEntry)p.GetUnderlyingObject();
1178 if ((p.ContextType == ContextType.ApplicationDirectory) || (p.Context.ServerInformation.OsVersion == DomainControllerMode.Win2k))
1187 if (p.ContextType != ContextType.ApplicationDirectory)
1189 // A users group membership that applies to a particular domain includes the domain's universal, local and global groups plus the
1190 // universal groups from every other domain in the forest that the user is a member of. To get this list we must contact both a GlobalCatalog to get the forest
1191 // universal list and a DC in the users domain to get the domain local groups which are not replicated to the GC.
1192 // If we happen to get a GC in the same domain as the user
1193 // then we don't also need a DC because the domain local group memberships will show up as well. The enumerator code that expands these lists must detect
1194 // duplicates because the list of global groups will show up on both the GC and DC.
1195 Debug.Assert(p.ContextType == ContextType.Domain);
1197 Forest forest = Forest.GetForest(new DirectoryContext(DirectoryContextType.Forest, this.DnsForestName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null));
1199 DirectoryContext dc = new DirectoryContext(DirectoryContextType.Domain, this.DnsDomainName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null);
1200 DomainController dd = DomainController.FindOne(dc);
1202 GlobalCatalog gc = null;
1206 gc = forest.FindGlobalCatalog();
1208 var gg = forest.FindAllGlobalCatalogs(dd.SiteName);
1209 foreach (GlobalCatalog g in gg)
1211 if (string.Equals(this.DnsDomainName, g.Domain.Name, StringComparison.OrdinalIgnoreCase))
1218 roots.Add(new DirectoryEntry("GC://" + gc.Name + "/" + p.DistinguishedName, this.credentials != null ? this.credentials.UserName : null, this.credentials != null ? this.credentials.Password : null, this.AuthTypes));
1220 if (!string.Equals(this.DnsDomainName, gc.Domain.Name, StringComparison.OrdinalIgnoreCase))
1223 roots.Add(principalDE);
1225 //Since the GC does not belong to the same domain (as the principal object passed)
1226 //We should make sure that we ignore domain local groups that we obtained from the cross-domain GC.
1227 resultValidator = delegate (dSPropertyCollection resultPropCollection)
1229 if (resultPropCollection["groupType"].Count > 0 && resultPropCollection["objectSid"].Count > 0)
1231 int? groupTypeValue = (int?)resultPropCollection["groupType"][0];
1232 if (groupTypeValue.HasValue && ((groupTypeValue.Value & ADGroupScope.Local) == ADGroupScope.Local))
1234 byte[] sidByteArray = (byte[])resultPropCollection["objectSid"][0];
1235 SecurityIdentifier resultSid = new SecurityIdentifier(sidByteArray, 0);
1236 return ADUtils.AreSidsInSameDomain(p.Sid, resultSid);
1239 return true; //Return true for all other case, including the case where we don't have permissions
1240 //to read groupType/objectSid attribute then we declare the result as a match.
1244 catch (System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException e)
1246 // if we can't get a GC then just fail.
1247 throw new PrincipalOperationException(e.Message, e);
1249 catch (System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException e)
1251 // if we can't get a GC then just fail.
1252 throw new PrincipalOperationException(e.Message, e);
1267 if (false == useASQ)
1269 // If this is ADAM then we only need to use the original object.
1270 // IF AD then we will use whatever enumerators we discovered above.
1271 if (p.ContextType != ContextType.ApplicationDirectory)
1274 enumerators = new IEnumerable[roots.Count];
1276 foreach (DirectoryEntry de in roots)
1278 //If, de is not equal to principalDE then it must have been created by this function (above code)
1279 //In that case de is NOT owned by any other modules outside. Hence, configure RangeRetriever to dispose the DirEntry on its dispose.
1280 enumerators[index] = new RangeRetriever(de, "memberOf", (de != principalDE));
1286 enumerators = new IEnumerable[1];
1287 //Since principalDE is not owned by us,
1288 //configuring RangeRetriever _NOT_ to dispose the DirEntry on its dispose.
1289 enumerators[0] = new RangeRetriever(principalDE, "memberOf", false);
1295 searchers = new DirectorySearcher[roots.Count];
1297 foreach (DirectoryEntry de in roots)
1299 searchers[index] = SDSUtils.ConstructSearcher(de);
1300 searchers[index].SearchScope = SearchScope.Base;
1301 searchers[index].AttributeScopeQuery = "memberOf";
1302 searchers[index].Filter = "(objectClass=*)";
1303 searchers[index].CacheResults = false;
1305 BuildPropertySet(typeof(GroupPrincipal), searchers[index].PropertiesToLoad);
1310 string principalDN = (string)principalDE.Properties["distinguishedName"].Value;
1312 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: principalDN={0}", principalDN);
1314 principalDE.RefreshCache(new string[] { "memberOf", "primaryGroupID" });
1316 if ((principalDE.Properties["primaryGroupID"].Count > 0) &&
1317 (principalDE.Properties["objectSid"].Count > 0))
1319 Debug.Assert(principalDE.Properties["primaryGroupID"].Count == 1);
1320 Debug.Assert(principalDE.Properties["objectSid"].Count == 1);
1322 int primaryGroupID = (int)principalDE.Properties["primaryGroupID"].Value;
1323 byte[] principalSid = (byte[])principalDE.Properties["objectSid"].Value;
1325 primaryGroupDN = GetGroupDnFromGroupID(principalSid, primaryGroupID);
1326 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf: primary group DN={0}", primaryGroupDN);
1329 // We must use enumeration to expand the users group membership
1330 // ASQ will not work because we cannot correctly generate referrals if one of the users
1331 // groups if from another domain in the forest.
1335 if (resultValidator != null)
1337 resultSet = new ADDNConstraintLinkedAttrSet(
1338 ADDNConstraintLinkedAttrSet.ConstraintType.ResultValidatorDelegateMatch,
1339 resultValidator, principalDN, searchers, primaryGroupDN, null, false, this);
1343 resultSet = new ADDNLinkedAttrSet(principalDN, searchers, primaryGroupDN, null, false, this);
1348 if (resultValidator != null)
1350 resultSet = new ADDNConstraintLinkedAttrSet(
1351 ADDNConstraintLinkedAttrSet.ConstraintType.ResultValidatorDelegateMatch,
1352 resultValidator, principalDN, enumerators, primaryGroupDN, null, false, this);
1356 resultSet = new ADDNLinkedAttrSet(principalDN, enumerators, primaryGroupDN, null, false, this);
1361 catch (System.Runtime.InteropServices.COMException e)
1363 throw ExceptionHelper.GetExceptionFromCOMException(e);
1367 if (null != gcPrincipalDe)
1369 gcPrincipalDe.Dispose();
1372 if (null != memberOfSearcher)
1374 memberOfSearcher.Dispose();
1379 // Get groups from this ctx which contain a principal corresponding to foreignPrincipal
1380 // (which is a principal from foreignContext)
1381 internal override ResultSet GetGroupsMemberOf(Principal foreignPrincipal, StoreCtx foreignContext)
1383 // Get the Principal's SID, so we can look it up by SID in our store
1384 SecurityIdentifier Sid = foreignPrincipal.Sid;
1387 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery);
1389 // Search our store for a object with a matching SID. This could be a user/group/computer object,
1390 // or a foreignSecurityPrincipal. Doesn't really matter --- either way, the store object will have a objectSid
1391 // and a memberOf attribute.
1395 // If we can read the defaultNamingContext and retrive the well known path for the foreignSecurityPrincipal container start there.
1396 // If we can only read the defaultNamingContext then start there
1397 // Else just start at the base DN from the original context
1399 // If the object was not found and we started at teh fsp container then search the entire DC.
1400 // Else just exit. we have nothing else to search
1402 // An object exists in the domain that contains links to all the groups it is a member of.
1403 bool rootPrincipalExists = true;
1405 if ((foreignContext is ADStoreCtx) && !(foreignContext is ADAMStoreCtx))
1407 // We only need to check forest status for AD stores. Forest concept does not apply to ADAM.
1408 ADStoreCtx foreignADStore = (ADStoreCtx)foreignContext;
1410 // If same forest but different domain then we have a child or alternate tree domain. We don't have a starting user
1411 // object and must do a search on all groups to find membership.
1412 if (string.Equals(foreignADStore.DnsForestName, this.DnsForestName, StringComparison.OrdinalIgnoreCase))
1414 if (string.Equals(foreignADStore.DnsDomainName, this.DnsDomainName, StringComparison.OrdinalIgnoreCase))
1416 rootPrincipalExists = true;
1420 rootPrincipalExists = false;
1425 DirectoryEntry dncContainer = null;
1426 string fspWkDn = null;
1427 DirectoryEntry fspContainer = null;
1428 ResultSet resultSet = null;
1429 DirectorySearcher ds = null;
1433 if (rootPrincipalExists)
1435 if (this.DefaultNamingContext != null)
1437 dncContainer = new DirectoryEntry(@"LDAP://" + this.UserSuppliedServerName + @"/" + this.DefaultNamingContext, Credentials != null ? this.Credentials.UserName : null, Credentials != null ? this.Credentials.Password : null, this.AuthTypes);
1439 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): Read DNC of {0}", this.DefaultNamingContext);
1441 fspWkDn = ADUtils.RetriveWkDn(dncContainer, this.DefaultNamingContext, this.UserSuppliedServerName, Constants.GUID_FOREIGNSECURITYPRINCIPALS_CONTAINER_BYTE);
1443 if (null != fspWkDn)
1445 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): Read fsp DN {0}", fspWkDn);
1446 fspContainer = new DirectoryEntry(fspWkDn, Credentials != null ? this.credentials.UserName : null, Credentials != null ? this.credentials.Password : null, this.authTypes);
1450 ds = new DirectorySearcher((fspContainer != null) ? fspContainer : ((dncContainer != null ? dncContainer : this.ctxBase)));
1452 // Pick some reasonable default values
1454 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds
1456 // Build the LDAP filter
1457 // Converr the object to a SDDL format
1458 string stringSid = Utils.SecurityIdentifierToLdapHexFilterString(Sid);
1459 if (stringSid == null)
1460 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery);
1462 ds.Filter = "(objectSid=" + stringSid + ")";
1464 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): using LDAP filter {0}", ds.Filter);
1466 // We only need a few attributes
1467 ds.PropertiesToLoad.Add("memberOf");
1468 ds.PropertiesToLoad.Add("distinguishedName");
1469 ds.PropertiesToLoad.Add("primaryGroupID");
1470 ds.PropertiesToLoad.Add("objectSid");
1472 // If no corresponding principal exists in this store, then by definition the principal isn't
1473 // a member of any groups in this store.
1474 SearchResult sr = ds.FindOne();
1478 // no match so we better do a root level search in case we are targetting a domain where
1479 // the user is not an FSP.
1481 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): No match");
1483 // We already did a root level search so just exit.
1485 if (null == fspWkDn)
1486 return new EmptySet();
1488 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): performing DNC level search");
1490 ds.SearchRoot = dncContainer;
1494 return new EmptySet();
1497 // Now that we found the corresponding principal, the rest is very similiar to the plain GetGroupsMemberOf()
1498 // case, exception we're working with search results (SearchResult/ResultPropertyValueCollection) rather
1499 // than DirectoryEntry/PropertyValueCollection.
1500 string principalDN = (string)sr.Properties["distinguishedName"][0];
1501 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): match, DN={0}", principalDN);
1503 //Here a new DirectoryEntry object is created by sr.GetDirectoryEntry() and passed
1504 //to RangeRetriever object. Hence, configuring RangeRetriever to dispose the DirEntry on its dispose.
1505 IEnumerable memberOf = new RangeRetriever(sr.GetDirectoryEntry(), "memberOf", true);
1507 string primaryGroupDN = null;
1509 if ((sr.Properties["primaryGroupID"].Count > 0) &&
1510 (sr.Properties["objectSid"].Count > 0))
1512 Debug.Assert(sr.Properties["primaryGroupID"].Count == 1);
1513 Debug.Assert(sr.Properties["objectSid"].Count == 1);
1515 int primaryGroupID = (int)sr.Properties["primaryGroupID"][0];
1516 byte[] principalSid = (byte[])sr.Properties["objectSid"][0];
1518 primaryGroupDN = GetGroupDnFromGroupID(principalSid, primaryGroupID);
1519 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupsMemberOf(ctx): primary group DN={0}", primaryGroupDN);
1522 resultSet = new ADDNConstraintLinkedAttrSet(ADDNConstraintLinkedAttrSet.ConstraintType.ContainerStringMatch, this.ctxBase.Properties["distinguishedName"].Value, principalDN, new IEnumerable[] { memberOf }, primaryGroupDN, null, false, this);
1526 // We don't need to retrive the Primary group ID here because we have already established that this user is not from this domain
1527 // and the users primary group must be from the same domain as the user.
1528 Debug.Assert(foreignPrincipal.ContextType != ContextType.ApplicationDirectory);
1530 DirectorySearcher[] memberSearcher = { SDSUtils.ConstructSearcher(this.ctxBase) };
1531 memberSearcher[0].Filter = "(&(objectClass=Group)(member=" + foreignPrincipal.DistinguishedName + "))";
1532 memberSearcher[0].CacheResults = false;
1534 resultSet = new ADDNLinkedAttrSet(foreignPrincipal.DistinguishedName, memberSearcher, null, null, false, this);
1539 catch (System.Runtime.InteropServices.COMException e)
1541 throw ExceptionHelper.GetExceptionFromCOMException(e);
1545 if (null != fspContainer)
1546 fspContainer.Dispose();
1549 if (null != dncContainer)
1550 dncContainer.Dispose();
1554 private string GetGroupDnFromGroupID(byte[] userSid, int primaryGroupId)
1556 IntPtr pGroupSid = IntPtr.Zero;
1557 byte[] groupSid = null;
1559 // This function is based on the technique in KB article 297951.
1563 string sddlSid = Utils.ConvertSidToSDDL(userSid);
1564 if (sddlSid != null)
1566 // Next, we modify the SDDL SID to replace with final subauthority
1567 // with the primary group's RID (the primaryGroupID)
1568 int index = sddlSid.LastIndexOf('-');
1572 sddlSid = sddlSid.Substring(0, index) + "-" + ((uint)primaryGroupId).ToString(CultureInfo.InvariantCulture);
1574 // Now, we convert the SDDL back into a SID
1575 if (UnsafeNativeMethods.ConvertStringSidToSid(sddlSid, ref pGroupSid))
1577 // Now we convert the native SID to a byte[] SID
1578 groupSid = Utils.ConvertNativeSidToByteArray(pGroupSid);
1585 if (pGroupSid != IntPtr.Zero)
1586 UnsafeNativeMethods.LocalFree(pGroupSid);
1589 if (groupSid != null)
1591 return "<SID=" + Utils.ByteArrayToString(groupSid) + ">";
1597 // Get groups of which p is a member, using AuthZ S4U APIs for recursive membership
1598 internal override ResultSet GetGroupsMemberOfAZ(Principal p)
1600 // Enforced by the methods that call us
1601 Debug.Assert(p.unpersisted == false);
1602 Debug.Assert(p is UserPrincipal);
1604 // Get the user SID that AuthZ will use.
1605 SecurityIdentifier SidObj = p.Sid;
1609 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "GetGroupsMemberOfAZ: no SID IC");
1610 throw new InvalidOperationException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery);
1613 byte[] sid = new byte[SidObj.BinaryLength];
1614 SidObj.GetBinaryForm(sid, 0);
1618 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "GetGroupsMemberOfAZ: bad SID IC");
1619 throw new ArgumentException(SR.StoreCtxSecurityIdentityClaimBadFormat);
1624 if (true == ADUtils.VerifyOutboundTrust(this.DnsDomainName, (this.credentials == null ? null : this.credentials.UserName), (this.credentials == null ? null : this.credentials.Password)))
1626 return new AuthZSet(sid, this.credentials, this.contextOptions, this.FlatDomainName, this, this.ctxBase);
1630 DirectoryEntry principalDE = (DirectoryEntry)p.UnderlyingObject;
1631 string principalDN = (string)principalDE.Properties["distinguishedName"].Value;
1633 return (new TokenGroupSet(principalDN, this, true));
1636 catch (System.Runtime.InteropServices.COMException e)
1638 throw ExceptionHelper.GetExceptionFromCOMException(e);
1642 // Get members of group g
1644 // 1. Users with this group as their primary group ID
1645 // 2. ASQ search against the member attribute on the group object for all contained objects.
1646 internal override BookmarkableResultSet GetGroupMembership(GroupPrincipal g, bool recursive)
1648 // Enforced by the methods that call us
1649 Debug.Assert(g.unpersisted == false);
1651 // Fake groups are a member of other groups, but they themselves have no members
1652 // (they don't even exist in the store)
1653 if (g.fakePrincipal)
1655 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: fake principal");
1656 return new EmptySet();
1659 Debug.Assert(g.UnderlyingObject != null);
1663 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject;
1665 // Create a DirectorySearcher for users who are members of this group via their primaryGroupId attribute
1666 DirectorySearcher ds = null;
1668 if (groupDE.Properties["objectSid"].Count > 0)
1670 Debug.Assert(groupDE.Properties["objectSid"].Count == 1);
1671 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0];
1673 ds = GetDirectorySearcherFromGroupID(groupSid);
1674 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: using LDAP filter={0}", ds.Filter);
1677 string groupDN = (string)groupDE.Properties["distinguishedName"].Value;
1678 BookmarkableResultSet resultSet = null;
1680 // We must use enumeration to expand groups if their scope is Universal or Local
1681 // or if the domain controller is w2k
1682 // or if the context type is ApplicationDirectory (in AD LDS, Global groups can contain members from other partition)
1683 // Universal and Local groups can contain members from other domains in the forest. When this occurs
1684 // the referral is not generated correctly and we get an error.
1686 if (g.Context.ContextType == ContextType.ApplicationDirectory ||
1687 g.Context.ServerInformation.OsVersion == DomainControllerMode.Win2k ||
1688 g.GroupScope != GroupScope.Global)
1690 //Here the directory entry passed to RangeRetriever constructor belongs to
1691 //the GroupPrincipal object supplied to this function, which is not owned by us.
1692 //Hence, configuring RangeRetriever _NOT_ to dispose the DirEntry on its dispose.
1693 IEnumerable members = new RangeRetriever(groupDE, "member", false);
1695 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "GetGroupMembership: groupDN={0}", groupDN);
1696 resultSet = new ADDNLinkedAttrSet(groupDN, new IEnumerable[] { members }, null, ds, recursive, this);
1700 DirectorySearcher[] dsMembers = new DirectorySearcher[1];
1701 dsMembers[0] = SDSUtils.ConstructSearcher((DirectoryEntry)g.UnderlyingObject);
1702 dsMembers[0].AttributeScopeQuery = "member";
1703 dsMembers[0].SearchScope = SearchScope.Base;
1704 dsMembers[0].Filter = "(objectClass=*)";
1705 dsMembers[0].CacheResults = false;
1707 BuildPropertySet(typeof(UserPrincipal), dsMembers[0].PropertiesToLoad);
1708 BuildPropertySet(typeof(GroupPrincipal), dsMembers[0].PropertiesToLoad);
1709 resultSet = new ADDNLinkedAttrSet(groupDN, dsMembers, null, ds, recursive, this);
1714 catch (System.Runtime.InteropServices.COMException e)
1716 throw ExceptionHelper.GetExceptionFromCOMException(e);
1720 private DirectorySearcher GetDirectorySearcherFromGroupID(byte[] groupSid)
1722 Debug.Assert(groupSid != null);
1724 // Get the group's RID from the group's SID
1725 int groupRid = Utils.GetLastRidFromSid(groupSid);
1727 // Build a DirectorySearcher for users whose primaryGroupId == the group's RID
1728 DirectorySearcher ds = new DirectorySearcher(this.ctxBase);
1729 ds.Filter = GetObjectClassPortion(typeof(Principal)) + "(primaryGroupId=" + groupRid.ToString(CultureInfo.InvariantCulture) + "))";
1731 // Pick some reasonable default values
1733 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds
1735 BuildPropertySet(typeof(Principal), ds.PropertiesToLoad);
1740 // Is p a member of g in the store?
1741 internal override bool SupportsNativeMembershipTest { get { return true; } }
1743 /// First check direct group membership by using DE.IsMember
1744 /// If this fails then we may have a ForeignSecurityPrincipal so search for Foreign Security Principals
1745 /// With the p's SID and then call IsMember with the ADS Path returned from the search.
1746 internal override bool IsMemberOfInStore(GroupPrincipal g, Principal p)
1748 Debug.Assert(g.unpersisted == false);
1749 Debug.Assert(p.unpersisted == false);
1751 // Consistent with GetGroupMembership, a group that is a fake principal has no members
1752 if (g.fakePrincipal)
1754 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: fake group");
1758 // AD Groups can only have AD principals as members
1759 if (p.ContextType != ContextType.Domain && p.ContextType != ContextType.ApplicationDirectory)
1761 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: member is not a domain principal");
1765 Debug.Assert(g.UnderlyingObject != null && g.UnderlyingObject is DirectoryEntry);
1766 UnsafeNativeMethods.IADsGroup adsGroup = (UnsafeNativeMethods.IADsGroup)((DirectoryEntry)g.UnderlyingObject).NativeObject;
1767 IEnumerable cachedMembersEnum = null; //This variables stores a reference to the direct members enumerator of the group.
1769 // Only real principals can be directly a member of the group, since only real principals
1770 // actually exist in the store.
1771 if (!p.fakePrincipal)
1773 Debug.Assert(p.UnderlyingObject != null && p.UnderlyingObject is DirectoryEntry);
1775 //// Test for direct membership
1778 DirectoryEntry principalDE = (DirectoryEntry)p.UnderlyingObject;
1779 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject;
1780 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: real principal, DN={0}", principalDE.Path);
1781 string principalDN = (string)principalDE.Properties["distinguishedName"].Value;
1783 // we want to find if a group is "small", meaning that it has less than MaxValRange values (usually 1500)
1784 // the property list for the searcher of a group has "member" attribute. if there are more results than MaxValRange, there will also be a "member;range=..." attribute
1785 if (g.IsSmallGroup())
1787 // small groups has special search object that holds the member attribute so we use it for our search (no need to use the DirectoryEntry)
1788 Debug.Assert(g.SmallGroupMemberSearchResult != null);
1789 cachedMembersEnum = g.SmallGroupMemberSearchResult.Properties["member"];
1790 if ((g.SmallGroupMemberSearchResult != null) && g.SmallGroupMemberSearchResult.Properties["member"].Contains(principalDN))
1797 // this is a large group. use range retrieval instead of simple attribute check.
1798 RangeRetriever rangeRetriever = new RangeRetriever(groupDE, "member", false);
1799 rangeRetriever.CacheValues = true;
1800 foreach (string memberDN in rangeRetriever)
1802 if (principalDN.Equals(memberDN, StringComparison.OrdinalIgnoreCase))
1807 rangeRetriever.Reset(); //Reset range-retriever enum, so that it can be traversed again.
1808 cachedMembersEnum = rangeRetriever;
1813 // Might be an FPO (either a real principal from another forest, or a fake principal
1814 // that AD represents as an FPO). Search for the FPO, and test its DN for membership.
1817 // Get the Principal's SID, so we can look it up by SID in our store
1818 SecurityIdentifier Sid = p.Sid;
1822 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: no SID IC or null UrnValue");
1823 throw new ArgumentException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery);
1825 DirectoryEntry defaultNCDirEntry = null;
1826 DirectorySearcher ds = null;
1830 string path = string.Format(
1831 CultureInfo.InvariantCulture,
1833 string.IsNullOrEmpty(this.UserSuppliedServerName) ? this.DnsHostName : this.UserSuppliedServerName,
1834 this.ContextBasePartitionDN
1837 defaultNCDirEntry = SDSUtils.BuildDirectoryEntry(path, this.credentials, this.authTypes);
1839 ds = new DirectorySearcher(defaultNCDirEntry);
1841 // Pick some reasonable default values
1842 ds.ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds
1844 // Build the LDAP filter, Convert the sid to SDDL format
1845 string stringSid = Utils.SecurityIdentifierToLdapHexFilterString(Sid);
1846 if (stringSid == null)
1848 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: bad SID IC");
1849 throw new ArgumentException(SR.StoreCtxNeedValueSecurityIdentityClaimToQuery);
1852 ds.Filter = "(&(objectClass=foreignSecurityPrincipal)(objectSid=" + stringSid + "))";
1853 GlobalDebug.WriteLineIf(GlobalDebug.Info,
1855 "IsMemberOfInStore: FPO principal, using LDAP filter {0}",
1858 ds.PropertiesToLoad.Add("distinguishedName");
1860 SearchResult sr = ds.FindOne();
1862 // No FPO ---> not a member
1865 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: no FPO found");
1868 string fpoDN = (string)sr.Properties["distinguishedName"][0];
1869 foreach (string memberDN in cachedMembersEnum)
1871 if (string.Equals(fpoDN, memberDN, StringComparison.OrdinalIgnoreCase))
1878 catch (System.Runtime.InteropServices.COMException e)
1880 throw ExceptionHelper.GetExceptionFromCOMException(e);
1888 if (defaultNCDirEntry != null)
1890 defaultNCDirEntry.Dispose();
1895 // The only reason a Clear() operation can not be performed on the group is if there
1896 // are one or more members on the store who are a member of the group by virtue of their
1897 // primaryGroupId, rather than by the group's "member" attribute.
1898 internal override bool CanGroupBeCleared(GroupPrincipal g, out string explanationForFailure)
1900 explanationForFailure = null;
1902 // If the group is unpersisted, there are certainly no principals in the store who
1903 // are a member of it by vitue of primaryGroupId. If the group is a fake group, it has no
1904 // members. Either way, they can clear it.
1905 if (g.unpersisted || g.fakePrincipal)
1907 GlobalDebug.WriteLineIf(GlobalDebug.Info,
1909 "CanGroupBeCleared: unpersisted={0}, fake={1}",
1915 Debug.Assert(g.UnderlyingObject != null);
1916 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject;
1918 // Create a DirectorySearcher for users who are members of this group via their primaryGroupId attribute
1919 DirectorySearcher ds = null;
1923 if (groupDE.Properties["objectSid"].Count > 0)
1925 Debug.Assert(groupDE.Properties["objectSid"].Count == 1);
1926 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0];
1928 ds = GetDirectorySearcherFromGroupID(groupSid);
1930 // We only need to know if there's at least one such user
1933 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: using LDAP filter {0}", ds.Filter);
1935 SearchResult sr = ds.FindOne();
1939 // there is such a member, we can't clear the group
1940 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: found member, can't clear");
1942 explanationForFailure = SR.ADStoreCtxCantClearGroup;
1947 // no such members, we can clear the group
1948 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "IsMemberOfInStore: no member, can clear");
1955 // We don't have sufficient information. Assume we can clear it.
1956 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "IsMemberOfInStore: can't search, assume can clear");
1960 catch (System.Runtime.InteropServices.COMException e)
1962 throw ExceptionHelper.GetExceptionFromCOMException(e);
1971 // The only reason we wouldn't be able to remove this member is if it's a member by virtue of its
1972 // primaryGroupId rather than the group's "member" attribute
1973 internal override bool CanGroupMemberBeRemoved(GroupPrincipal g, Principal member, out string explanationForFailure)
1975 explanationForFailure = null;
1977 // If the member is unpersisted, it has no primaryGroupId attribute that could point to the group.
1978 // If the member is a fake princiapl, it also has no primaryGroupId attribute that could point to the group.
1979 // So either way, we have no objections to it being removed from the group.
1980 if (member.unpersisted || member.fakePrincipal)
1982 GlobalDebug.WriteLineIf(GlobalDebug.Info,
1984 "CanGroupMemberBeRemoved: member unpersisted={0}, fake={1}",
1986 member.fakePrincipal);
1991 // If the group is unpersisted, then clearly there is no way the principal is
1992 // a member of it by vitue of primaryGroupId. If the group is a fake group, it has no
1993 // members and so we don't care about it.
1994 if (g.unpersisted || g.fakePrincipal)
1996 GlobalDebug.WriteLineIf(GlobalDebug.Info,
1998 "CanGroupMemberBeRemoved: group unpersisted={0}, fake={1}",
2004 // ADAM groups can have AD or ADAM members, AD groups can only have AD members
2005 //, but we could be called before our caller
2006 // has verified the principal being passed in as the member. Just ignore it if the member isn't an AD principal,
2007 // it'll be caught later in PrincipalCollection.Remove().
2008 if ((g.ContextType == ContextType.Domain && member.ContextType != ContextType.Domain) ||
2009 (member.ContextType != ContextType.Domain && member.ContextType != ContextType.ApplicationDirectory))
2011 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "CanGroupMemberBeRemoved: member is not a domain or application directory principal");
2017 Debug.Assert(g.UnderlyingObject != null);
2018 Debug.Assert(member.UnderlyingObject != null);
2019 DirectoryEntry groupDE = (DirectoryEntry)g.UnderlyingObject;
2020 DirectoryEntry memberDE = (DirectoryEntry)member.UnderlyingObject;
2022 if ((groupDE.Properties["objectSid"].Count > 0) &&
2023 (memberDE.Properties["primaryGroupID"].Count > 0))
2025 Debug.Assert(groupDE.Properties["objectSid"].Count == 1);
2026 Debug.Assert(memberDE.Properties["primaryGroupID"].Count == 1);
2028 byte[] groupSid = (byte[])groupDE.Properties["objectSid"][0];
2029 int primaryGroupID = (int)memberDE.Properties["primaryGroupID"][0];
2031 int groupRid = Utils.GetLastRidFromSid(groupSid);
2033 if (groupRid == primaryGroupID)
2035 // It is a primary group member, we can't remove it.
2036 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2038 "CanGroupMemberBeRemoved: primary group member (rid={0}), can't remove",
2041 explanationForFailure = SR.ADStoreCtxCantRemoveMemberFromGroup;
2046 // It's not a primary group member, we can remove it.
2047 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2049 "CanGroupMemberBeRemoved: not primary group member (group rid={0}, primary group={1}), can remove",
2058 // We don't have sufficient information. Assume we can remove it.
2059 // If we can't, we'll get an exception when we try to save the changes.
2060 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "CanGroupMemberBeRemoved: can't test, assume can remove");
2064 catch (System.Runtime.InteropServices.COMException e)
2066 throw ExceptionHelper.GetExceptionFromCOMException(e);
2071 // Cross-store support
2074 // Given a native store object that represents a "foreign" principal (e.g., a FPO object in this store that
2075 // represents a pointer to another store), maps that representation to the other store's StoreCtx and returns
2076 // a Principal from that other StoreCtx. The implementation of this method is highly dependent on the
2077 // details of the particular store, and must have knowledge not only of this StoreCtx, but also of how to
2078 // interact with other StoreCtxs to fulfill the request.
2080 // This method is typically used by ResultSet implementations, when they're iterating over a collection
2081 // (e.g., of group membership) and encounter an entry that represents a foreign principal.
2082 internal override Principal ResolveCrossStoreRefToPrincipal(object o)
2084 Debug.Assert(o is DirectoryEntry);
2085 Debug.Assert(ADUtils.IsOfObjectClass((DirectoryEntry)o, "foreignSecurityPrincipal"));
2089 // Get the SID of the foreign principal
2090 DirectoryEntry fpoDE = (DirectoryEntry)o;
2092 if (fpoDE.Properties["objectSid"].Count == 0)
2094 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: no objectSid found");
2095 throw new PrincipalOperationException(SR.ADStoreCtxCantRetrieveObjectSidForCrossStore);
2098 Debug.Assert(fpoDE.Properties["objectSid"].Count == 1);
2100 byte[] sid = (byte[])fpoDE.Properties["objectSid"].Value;
2102 // What type of SID is it?
2103 SidType sidType = Utils.ClassifySID(sid);
2105 if (sidType == SidType.FakeObject)
2107 // It's a FPO for something like NT AUTHORITY\NETWORK SERVICE.
2108 // There's no real store object corresponding to this FPO, so
2109 // fake a Principal.
2110 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2112 "ResolveCrossStoreRefToPrincipal: fake principal, SID={0}",
2113 Utils.ByteArrayToString(sid));
2115 return ConstructFakePrincipalFromSID(sid);
2118 StoreCtx foreignStoreCtx;
2119 if (sidType == SidType.RealObjectFakeDomain)
2121 // This is a BUILTIN object. It's a real object on the store we're connected to, but LookupSid
2122 // will tell us it's a member of the BUILTIN domain. Resolve it as a principal on our store.
2123 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: builtin principal");
2125 foreignStoreCtx = this;
2129 // Ask the OS to resolve the SID to its target.
2130 UnsafeNativeMethods.IAdsObjectOptions objOptions = (UnsafeNativeMethods.IAdsObjectOptions)this.ctxBase.NativeObject;
2131 string serverName = (string)objOptions.GetOption(0 /* == ADS_OPTION_SERVERNAME */);
2133 int accountUsage = 0;
2138 int err = Utils.LookupSid(serverName, this.credentials, sid, out name, out domainName, out accountUsage);
2142 GlobalDebug.WriteLineIf(GlobalDebug.Warn,
2144 "ResolveCrossStoreRefToPrincipal: LookupSid failed, err={0}, server={1}",
2148 throw new PrincipalOperationException(
2149 string.Format(CultureInfo.CurrentCulture,
2150 SR.ADStoreCtxCantResolveSidForCrossStore,
2154 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2156 "ResolveCrossStoreRefToPrincipal: LookupSid found {0} in {1}",
2160 // Since this is AD, the remote principal must be an AD principal.
2161 // Build a PrincipalContext for the store which owns the principal
2163 // We are now connecting to AD so change to negotiate with signing and sealing
2165 ContextOptions remoteOptions = DefaultContextOptions.ADDefaultContextOption;
2168 PrincipalContext remoteCtx = SDSCache.Domain.GetContext(domainName, this.credentials, remoteOptions);
2170 PrincipalContext remoteCtx = new PrincipalContext(
2174 (this.credentials != null ? credentials.UserName : null),
2175 (this.credentials != null ? credentials.Password : null),
2179 foreignStoreCtx = remoteCtx.QueryCtx;
2182 Principal p = foreignStoreCtx.FindPrincipalByIdentRef(
2184 UrnScheme.SidScheme,
2185 (new SecurityIdentifier(sid, 0)).ToString(),
2192 GlobalDebug.WriteLineIf(GlobalDebug.Warn, "ADStoreCtx", "ResolveCrossStoreRefToPrincipal: no matching principal");
2193 throw new PrincipalOperationException(SR.ADStoreCtxFailedFindCrossStoreTarget);
2196 catch (System.Runtime.InteropServices.COMException e)
2198 throw ExceptionHelper.GetExceptionFromCOMException(e);
2202 // Returns true if AccountInfo is supported for the specified principal, false otherwise.
2203 // Used when an application tries to access the AccountInfo property of a newly-inserted
2204 // (not yet persisted) AuthenticablePrincipal, to determine whether it should be allowed.
2205 internal override bool SupportsAccounts(AuthenticablePrincipal p)
2207 // Fake principals do not have store objects, so they certainly don't have stored account info.
2208 if (p.fakePrincipal)
2211 // Computer is a subclass of user in AD, and both therefore support account info.
2215 // Returns the set of credential types supported by this store for the specified principal.
2216 // Used when an application tries to access the PasswordInfo property of a newly-inserted
2217 // (not yet persisted) AuthenticablePrincipal, to determine whether it should be allowed.
2218 // Also used to implement AuthenticablePrincipal.SupportedCredentialTypes.
2219 internal override CredentialTypes SupportedCredTypes(AuthenticablePrincipal p)
2221 // Fake principals do not have store objects, so they certainly don't have stored creds.
2222 if (p.fakePrincipal)
2223 return (CredentialTypes)0;
2225 return CredentialTypes.Password | CredentialTypes.Certificate;
2229 // When called, this function does a GetInfoEx() to preload the DirectoryEntry's
2230 // property cache with all the attributes we'll be using. This avoids DirectoryEntry
2231 // doing an implicit GetInfo() and pulling down every attribute.
2233 // This function is currently loading every attribute from the directory instead of using the known list.
2234 internal void LoadDirectoryEntryAttributes(DirectoryEntry de)
2236 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadDirectoryEntryAttributes, path={0}", de.Path);
2238 string[] ldapAttributesUsed = new string[]
2244 "distinguishedName",
2252 "lastLogonTimestamp",
2259 "msDS-User-Account-Control-Computed",
2260 "ntSecurityDescriptor",
2268 "servicePrincipalName",
2271 "userAccountControl",
2273 "userPrincipalName",
2278 // de.RefreshCache(ldapAttributesUsed);
2281 catch (System.Runtime.InteropServices.COMException e)
2283 throw ExceptionHelper.GetExceptionFromCOMException(e);
2288 // Construct a fake Principal to represent a well-known SID like
2289 // "\Everyone" or "NT AUTHORITY\NETWORK SERVICE"
2291 internal override Principal ConstructFakePrincipalFromSID(byte[] sid)
2293 UnsafeNativeMethods.IAdsObjectOptions objOptions = (UnsafeNativeMethods.IAdsObjectOptions)this.ctxBase.NativeObject;
2294 string serverName = (string)objOptions.GetOption(0 /* == ADS_OPTION_SERVERNAME */);
2296 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2298 "ConstructFakePrincipalFromSID: sid={0}, server={1}, authority={2}",
2299 Utils.ByteArrayToString(sid),
2301 this.DnsDomainName);
2303 Principal p = Utils.ConstructFakePrincipalFromSID(
2308 this.DnsDomainName);
2310 // Assign it a StoreKey
2311 ADStoreKey key = new ADStoreKey(this.DnsDomainName, sid);
2323 /// Returns the DN of the Partition to which the user supplied
2324 /// context base (this.ctxBase) belongs.
2327 internal string ContextBasePartitionDN
2331 if (this.contextBasePartitionDN == null)
2333 lock (this.domainInfoLock)
2335 if (contextBasePartitionDN == null)
2340 return this.contextBasePartitionDN;
2344 internal string DefaultNamingContext
2348 if (this.defaultNamingContext == null)
2350 lock (this.domainInfoLock)
2352 if (defaultNamingContext == null)
2357 return this.defaultNamingContext;
2361 private string FlatDomainName
2365 if (this.domainFlatName == null)
2367 lock (this.domainInfoLock)
2369 if (domainFlatName == null)
2374 return this.domainFlatName;
2378 internal string DnsDomainName
2382 if (this.domainDnsName == null)
2384 lock (this.domainInfoLock)
2386 if (domainDnsName == null)
2391 return this.domainDnsName;
2395 internal string DnsHostName
2399 if (this.dnsHostName == null)
2401 lock (this.domainInfoLock)
2403 if (dnsHostName == null)
2408 return this.dnsHostName;
2412 internal string DnsForestName
2416 if (this.forestDnsName == null)
2418 lock (this.domainInfoLock)
2420 if (forestDnsName == null)
2425 return this.forestDnsName;
2429 internal string UserSuppliedServerName
2433 if (this.userSuppliedServerName == null)
2435 lock (this.domainInfoLock)
2437 if (this.userSuppliedServerName == null)
2442 return this.userSuppliedServerName;
2446 private ulong LockoutDuration
2450 // We test domainDnsName for null because lockoutDuration could legitimately be 0,
2451 // but lockoutDuration is valid iff domainDnsName is non-null
2452 if (this.domainDnsName == null)
2454 lock (this.domainInfoLock)
2456 if (domainDnsName == null)
2461 return this.lockoutDuration;
2465 protected object domainInfoLock = new object();
2466 protected string domainFlatName = null;
2467 protected string domainDnsName = null;
2468 protected string forestDnsName = null;
2469 protected string userSuppliedServerName = null;
2470 protected string defaultNamingContext = null;
2471 protected string contextBasePartitionDN = null; //contains the DN of the Partition to which the user supplied context base (this.ctxBase) belongs.
2472 protected string dnsHostName = null;
2473 protected ulong lockoutDuration = 0;
2475 protected enum StoreCapabilityMap
2480 protected StoreCapabilityMap storeCapability = 0;
2482 // Must be called inside of lock(domainInfoLock)
2483 virtual protected void LoadDomainInfo()
2485 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo");
2487 Debug.Assert(this.ctxBase != null);
2493 // We need to connect to the server's rootDse to get the naming context
2494 // From that, we can build the DNS Domain Name
2495 this.dnsHostName = ADUtils.GetServerName(this.ctxBase);
2497 string dnsDomainName = "";
2499 using (DirectoryEntry rootDse = new DirectoryEntry("LDAP://" + this.dnsHostName + "/rootDse", "", "", AuthenticationTypes.Anonymous))
2501 this.defaultNamingContext = (string)rootDse.Properties["defaultNamingContext"][0];
2502 this.contextBasePartitionDN = this.defaultNamingContext;
2504 // Split the naming context's DN into its RDNs
2505 string[] ncComponents = defaultNamingContext.Split(new char[] { ',' });
2507 StringBuilder sb = new StringBuilder();
2509 // Reassemble the RDNs (minus the tag) into the DNS domain name
2510 foreach (string component in ncComponents)
2512 // If it's not a "DC=" component, skip it
2513 if ((component.Length > 3) &&
2514 string.Equals(component.Substring(0, 3), "DC=", StringComparison.OrdinalIgnoreCase))
2516 sb.Append(component, 3, component.Length - 3);
2521 dnsDomainName = sb.ToString();
2523 // The loop added an extra period at the end. Remove it.
2524 if (dnsDomainName.Length > 0)
2525 dnsDomainName = dnsDomainName.Substring(0, dnsDomainName.Length - 1);
2527 this.domainDnsName = dnsDomainName;
2528 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using DNS domain name {0}", dnsDomainName);
2532 // NetBios (flat) Domain Name, and DNS Forest Name
2534 // Given the DNS domain name, we use DsGetDcName to get the flat name.
2535 // The same DsGetDcName call also retrieves the DNS forest name as a side effect.
2538 // DS_IS_DNS_NAME | DS_RETURN_FLAT_NAME | DS_DIRECTORY_SERVICE_REQUIRED | DS_BACKGROUND_ONLY
2539 int flags = unchecked((int)(0x00020000 | 0x80000000 | 0x00000010 | 0x00000100));
2540 UnsafeNativeMethods.DomainControllerInfo info = Utils.GetDcName(null, dnsDomainName, null, flags);
2542 this.domainFlatName = info.DomainName;
2543 this.forestDnsName = info.DnsForestName;
2545 GlobalDebug.WriteLineIf(GlobalDebug.Info,
2547 "LoadComputerInfo: using domainFlatName={0}, forestDnsName={1}",
2548 this.domainFlatName,
2549 this.forestDnsName);
2554 // Query the domain NC head to determine the lockout duration. Note that this is stored
2555 // on the server as a negative filetime.
2557 DirectoryEntry domainNC = SDSUtils.BuildDirectoryEntry(
2558 "LDAP://" + this.dnsHostName + "/" + this.defaultNamingContext,
2562 // So we don't load every property
2563 domainNC.RefreshCache(new string[] { "lockoutDuration" });
2565 if (domainNC.Properties["lockoutDuration"].Count > 0)
2567 Debug.Assert(domainNC.Properties["lockoutDuration"].Count == 1);
2568 long negativeLockoutDuration = ADUtils.LargeIntToInt64((UnsafeNativeMethods.IADsLargeInteger)domainNC.Properties["lockoutDuration"][0]);
2569 Debug.Assert(negativeLockoutDuration <= 0);
2570 ulong lockoutDuration = (ulong)(-negativeLockoutDuration);
2571 this.lockoutDuration = lockoutDuration;
2573 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using lockout duration {0}", lockoutDuration);
2577 // User supplied name
2579 UnsafeNativeMethods.Pathname pathCracker = new UnsafeNativeMethods.Pathname();
2580 UnsafeNativeMethods.IADsPathname pathName = (UnsafeNativeMethods.IADsPathname)pathCracker;
2582 pathName.Set(this.ctxBase.Path, 1 /* ADS_SETTYPE_FULL */);
2586 this.userSuppliedServerName = pathName.Retrieve(9 /*ADS_FORMAT_SERVER */);
2587 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using user-supplied name {0}", this.userSuppliedServerName);
2589 catch (COMException e)
2591 if (((uint)e.ErrorCode) == ((uint)0x80005000)) // E_ADS_BAD_PATHNAME
2594 GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "LoadComputerInfo: using empty string as user-supplied name");
2595 this.userSuppliedServerName = "";
2599 GlobalDebug.WriteLineIf(GlobalDebug.Error,
2601 "LoadComputerInfo: caught COMException {0} {1} looking for user-supplied name",
2610 internal override bool IsValidProperty(Principal p, string propertyName)
2612 return ((Hashtable)s_propertyMappingTableByProperty[this.MappingTableIndex]).Contains(propertyName);