|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.DirectoryServices;
using System.Globalization;
using System.Security.Principal;
using System.Text;
namespace System.DirectoryServices.AccountManagement
{
internal partial class ADStoreCtx : StoreCtx
{
//
// Query operations
//
// Returns true if this store has native support for search (and thus a wormhole).
// Returns true for everything but SAM (both reg-SAM and MSAM).
internal override bool SupportsSearchNatively { get { return true; } }
// Returns a type indicating the type of object that would be returned as the wormhole for the specified
// PrincipalSearcher.
internal override Type SearcherNativeType() { return typeof(DirectorySearcher); }
private void BuildExtensionPropertyList(Hashtable propertyList, Type p)
{
System.Reflection.PropertyInfo[] propertyInfoList = p.GetProperties();
foreach (System.Reflection.PropertyInfo pInfo in propertyInfoList)
{
DirectoryPropertyAttribute[] pAttributeList = (DirectoryPropertyAttribute[])(pInfo.GetCustomAttributes(typeof(DirectoryPropertyAttribute), true));
foreach (DirectoryPropertyAttribute pAttribute in pAttributeList)
{
if (!propertyList.Contains(pAttribute.SchemaAttributeName))
propertyList.Add(pAttribute.SchemaAttributeName, pAttribute.SchemaAttributeName);
}
}
}
protected void BuildPropertySet(Type p, StringCollection propertySet)
{
if (TypeToLdapPropListMap[this.MappingTableIndex].TryGetValue(p, out StringCollection value))
{
string[] props = new string[value.Count];
value.CopyTo(props, 0);
propertySet.AddRange(props);
}
else
{
Type baseType;
if (p.IsSubclassOf(typeof(UserPrincipal)))
{
baseType = typeof(UserPrincipal);
}
else if (p.IsSubclassOf(typeof(GroupPrincipal)))
{
baseType = typeof(GroupPrincipal);
}
else if (p.IsSubclassOf(typeof(ComputerPrincipal)))
{
baseType = typeof(ComputerPrincipal);
}
else if (p.IsSubclassOf(typeof(AuthenticablePrincipal)))
{
baseType = typeof(AuthenticablePrincipal);
}
else
{
baseType = typeof(Principal);
}
Hashtable propertyList = new Hashtable();
// Load the properties for the base types...
foreach (string s in TypeToLdapPropListMap[this.MappingTableIndex][baseType])
{
if (!propertyList.Contains(s))
{
propertyList.Add(s, s);
}
}
// Reflect the properties off the extension class and add them to the list.
BuildExtensionPropertyList(propertyList, p);
foreach (string property in propertyList.Values)
{
propertySet.Add(property);
}
// Cache the list for this property type so we don't need to reflect again in the future.
this.AddPropertySetToTypePropListMap(p, propertySet);
}
}
// Pushes the query represented by the QBE filter into the PrincipalSearcher's underlying native
// searcher object (creating a fresh native searcher and assigning it to the PrincipalSearcher if one
// doesn't already exist) and returns the native searcher.
// If the PrincipalSearcher does not have a query filter set (PrincipalSearcher.QueryFilter == null),
// produces a query that will match all principals in the store.
//
// For stores which don't have a native searcher (SAM), the StoreCtx
// is free to create any type of object it chooses to use as its internal representation of the query.
//
// Also adds in any clauses to the searcher to ensure that only principals, not mere
// contacts, are retrieved from the store.
internal override object PushFilterToNativeSearcher(PrincipalSearcher ps)
{
// This is the first time we're being called on this principal. Create a fresh searcher.
if (ps.UnderlyingSearcher == null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "PushFilterToNativeSearcher: creating fresh DirectorySearcher");
ps.UnderlyingSearcher = new DirectorySearcher(this.ctxBase);
((DirectorySearcher)ps.UnderlyingSearcher).PageSize = ps.PageSize;
((DirectorySearcher)ps.UnderlyingSearcher).ServerTimeLimit = new TimeSpan(0, 0, 30); // 30 seconds
}
DirectorySearcher ds = (DirectorySearcher)ps.UnderlyingSearcher;
Principal qbeFilter = ps.QueryFilter;
StringBuilder ldapFilter = new StringBuilder();
if (qbeFilter == null)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "PushFilterToNativeSearcher: no qbeFilter specified");
// No filter specified. Search for all principals (all users, computers, groups).
ldapFilter.Append("(|(objectClass=user)(objectClass=computer)(objectClass=group))");
}
else
{
//
// Start by appending the appropriate objectClass given the Principal type
//
ldapFilter.Append(GetObjectClassPortion(qbeFilter.GetType()));
//
// Next, fill in the properties (if any)
//
QbeFilterDescription filters = BuildQbeFilterDescription(qbeFilter);
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "PushFilterToNativeSearcher: using {0} filters", filters.FiltersToApply.Count);
Hashtable filterTable = (Hashtable)s_filterPropertiesTable[this.MappingTableIndex];
foreach (FilterBase filter in filters.FiltersToApply)
{
FilterPropertyTableEntry entry = (FilterPropertyTableEntry)filterTable[filter.GetType()];
if (entry == null)
{
// Must be a property we don't support
throw new InvalidOperationException(
SR.Format(
SR.StoreCtxUnsupportedPropertyForQuery,
PropertyNamesExternal.GetExternalForm(filter.PropertyName)));
}
ldapFilter.Append(entry.converter(filter, entry.suggestedADPropertyName));
}
//
// Wrap off the filter
//
ldapFilter.Append(')');
// We don't need any attributes returned, since we're just going to get a DirectoryEntry
// for the result. Per RFC 2251, OID 1.1 == no attributes.
//ds.PropertiesToLoad.Add("1.1");
BuildPropertySet(qbeFilter.GetType(), ds.PropertiesToLoad);
}
ds.Filter = ldapFilter.ToString();
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "PushFilterToNativeSearcher: using LDAP filter {0}", ds.Filter);
return ds;
}
protected virtual string GetObjectClassPortion(Type principalType)
{
string ldapFilter;
if (principalType == typeof(UserPrincipal))
ldapFilter = "(&(objectCategory=user)(objectClass=user)"; // objCat because we don't want to match on computer accounts
else if (principalType == typeof(GroupPrincipal))
ldapFilter = "(&(objectClass=group)";
else if (principalType == typeof(ComputerPrincipal))
ldapFilter = "(&(objectClass=computer)";
else if (principalType == typeof(Principal))
ldapFilter = "(&(|(objectClass=user)(objectClass=group))";
else if (principalType == typeof(AuthenticablePrincipal))
ldapFilter = "(&(objectClass=user)";
else
{
string objClass = ExtensionHelper.ReadStructuralObjectClass(principalType);
if (null == objClass)
{
Debug.Fail($"ADStoreCtx.GetObjectClassPortion: fell off end looking for {principalType}");
throw new InvalidOperationException(SR.Format(SR.StoreCtxUnsupportedPrincipalTypeForQuery, principalType));
}
ldapFilter = $"(&(objectClass={objClass})";
}
return ldapFilter;
}
// The core query operation.
// Given a PrincipalSearcher containg a query filter, transforms it into the store schema
// and performs the query to get a collection of matching native objects (up to a maximum of sizeLimit,
// or uses the sizelimit already set on the DirectorySearcher if sizeLimit == -1).
// If the PrincipalSearcher does not have a query filter (PrincipalSearcher.QueryFilter == null),
// matches all principals in the store.
//
// The collection may not be complete, i.e., paging - the returned ResultSet will automatically
// page in additional results as needed.
internal override ResultSet Query(PrincipalSearcher ps, int sizeLimit)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "Query");
try
{
// Set up the DirectorySearcher
DirectorySearcher ds = (DirectorySearcher)PushFilterToNativeSearcher(ps);
int oldSizeLimit = ds.SizeLimit;
Debug.Assert(sizeLimit >= -1);
if (sizeLimit != -1)
ds.SizeLimit = sizeLimit;
// Perform the actual search
SearchResultCollection src = ds.FindAll();
Debug.Assert(src != null);
// Create a ResultSet for the search results
ADEntriesSet resultSet = new ADEntriesSet(src, this, ps.QueryFilter.GetType());
ds.SizeLimit = oldSizeLimit;
return resultSet;
}
catch (System.Runtime.InteropServices.COMException e)
{
throw ExceptionHelper.GetExceptionFromCOMException(e);
}
}
//
// Query tables
//
// We only list properties we support filtering on in this table. At run-time, if we detect they set a
// property that's not listed here, we throw an exception.
private static readonly object[,] s_filterPropertiesTableRaw =
{
// QbeType AD property Converter
{typeof(DescriptionFilter), "description", new FilterConverterDelegate(StringConverter)},
{typeof(DisplayNameFilter), "displayName", new FilterConverterDelegate(StringConverter)},
{typeof(IdentityClaimFilter), "", new FilterConverterDelegate(IdentityClaimConverter)},
{typeof(SamAccountNameFilter), "sAMAccountName", new FilterConverterDelegate(StringConverter)},
{typeof(DistinguishedNameFilter), "distinguishedName", new FilterConverterDelegate(StringConverter)},
{typeof(GuidFilter), "objectGuid", new FilterConverterDelegate(GuidConverter)},
{typeof(UserPrincipalNameFilter), "userPrincipalName", new FilterConverterDelegate(StringConverter)},
{typeof(StructuralObjectClassFilter), "objectClass", new FilterConverterDelegate(StringConverter)},
{typeof(NameFilter), "name", new FilterConverterDelegate(StringConverter)},
{typeof(CertificateFilter), "", new FilterConverterDelegate(CertificateConverter)},
{typeof(AuthPrincEnabledFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(PermittedWorkstationFilter), "userWorkstations", new FilterConverterDelegate(CommaStringConverter)},
{typeof(PermittedLogonTimesFilter), "logonHours", new FilterConverterDelegate(BinaryConverter)},
{typeof(ExpirationDateFilter), "accountExpires", new FilterConverterDelegate(ExpirationDateConverter)},
{typeof(SmartcardLogonRequiredFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(DelegationPermittedFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(HomeDirectoryFilter), "homeDirectory", new FilterConverterDelegate(StringConverter)},
{typeof(HomeDriveFilter), "homeDrive", new FilterConverterDelegate(StringConverter)},
{typeof(ScriptPathFilter), "scriptPath", new FilterConverterDelegate(StringConverter)},
{typeof(PasswordNotRequiredFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(PasswordNeverExpiresFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(CannotChangePasswordFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(AllowReversiblePasswordEncryptionFilter), "userAccountControl", new FilterConverterDelegate(UserAccountControlConverter)},
{typeof(GivenNameFilter), "givenName", new FilterConverterDelegate(StringConverter)},
{typeof(MiddleNameFilter), "middleName", new FilterConverterDelegate(StringConverter)},
{typeof(SurnameFilter), "sn", new FilterConverterDelegate(StringConverter)},
{typeof(EmailAddressFilter), "mail", new FilterConverterDelegate(StringConverter)},
{typeof(VoiceTelephoneNumberFilter), "telephoneNumber", new FilterConverterDelegate(StringConverter)},
{typeof(EmployeeIDFilter), "employeeID", new FilterConverterDelegate(StringConverter)},
{typeof(GroupIsSecurityGroupFilter), "groupType", new FilterConverterDelegate(GroupTypeConverter)},
{typeof(GroupScopeFilter), "groupType", new FilterConverterDelegate(GroupTypeConverter)},
{typeof(ServicePrincipalNameFilter), "servicePrincipalName", new FilterConverterDelegate(StringConverter)},
{typeof(ExtensionCacheFilter), null, new FilterConverterDelegate(ExtensionCacheConverter)},
{typeof(BadPasswordAttemptFilter), "badPasswordTime", new FilterConverterDelegate(DefaultValutMatchingDateTimeConverter)},
{typeof(ExpiredAccountFilter), "accountExpires", new FilterConverterDelegate(MatchingDateTimeConverter)},
{typeof(LastLogonTimeFilter), "lastLogon", new FilterConverterDelegate(LastLogonConverter)},
{typeof(LockoutTimeFilter), "lockoutTime", new FilterConverterDelegate(MatchingDateTimeConverter)},
{typeof(PasswordSetTimeFilter), "pwdLastSet", new FilterConverterDelegate(DefaultValutMatchingDateTimeConverter)},
{typeof(BadLogonCountFilter), "badPwdCount", new FilterConverterDelegate(MatchingIntConverter)}
};
private static Hashtable s_filterPropertiesTable;
private sealed class FilterPropertyTableEntry
{
internal string suggestedADPropertyName;
internal FilterConverterDelegate converter;
}
//
// Conversion routines
//
// returns LDAP filter clause, e.g., "(description=foo*")
protected delegate string FilterConverterDelegate(FilterBase filter, string suggestedAdProperty);
protected static string StringConverter(FilterBase filter, string suggestedAdProperty)
{
return filter.Value != null ?
$"({suggestedAdProperty}={ADUtils.PAPIQueryToLdapQueryString((string)filter.Value)})" :
$"(!({suggestedAdProperty}=*))";
}
protected static string AcctDisabledConverter(FilterBase filter, string suggestedAdProperty)
{
// Principal property is AccountEnabled where TRUE = enabled FALSE = disabled. In ADAM
// this is stored as accountDisabled where TRUE = disabled and FALSE = enabled so here we need to revese the value.
return filter.Value != null ?
$"({suggestedAdProperty}={(!(bool)filter.Value ? "TRUE" : "FALSE")})" :
$"(!({suggestedAdProperty}=*))";
}
// Use this function when searching for an attribute where the absence of the attribute = a default setting.
// i.e. ms-DS-UserPasswordNotRequired in ADAM where non existence equals false.
protected static string DefaultValueBoolConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(NonPresentAttrDefaultStateMapping != null);
Debug.Assert(NonPresentAttrDefaultStateMapping.ContainsKey(suggestedAdProperty));
if (filter.Value == null)
{
return $"(!({suggestedAdProperty}=*))";
}
bool defaultState = NonPresentAttrDefaultStateMapping[suggestedAdProperty];
if (defaultState == (bool)filter.Value)
{
return $"(|(!({suggestedAdProperty}=*)({suggestedAdProperty}={((bool)filter.Value ? "TRUE" : "FALSE")})))";
}
return $"({suggestedAdProperty}={((bool)filter.Value ? "TRUE" : "FALSE")})";
}
protected static string CommaStringConverter(FilterBase filter, string suggestedAdProperty)
{
return filter.Value != null ?
$"({suggestedAdProperty}=*{ADUtils.PAPIQueryToLdapQueryString((string)filter.Value)}*)" :
$"(!({suggestedAdProperty}=*))";
}
protected static bool IdentityClaimToFilter(string identity, string identityFormat, ref string filter, bool throwOnFail)
{
identity ??= "";
StringBuilder sb = new StringBuilder();
switch (identityFormat)
{
case UrnScheme.GuidScheme:
// Transform from hex string ("1AFF") to LDAP hex string ("\1A\FF")
// The string passed is the string format of a GUID. We neeed to convert it into the ldap hex string
// to build a query
Guid g;
try
{
g = new Guid(identity);
}
catch (FormatException e)
{
if (throwOnFail)
// For now throw an exception to let the caller know the type was invalid.
throw new ArgumentException(e.Message, e);
else
return false;
}
byte[] gByte = g.ToByteArray();
StringBuilder stringguid = new StringBuilder();
foreach (byte b in gByte)
{
stringguid.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
string ldapHexGuid = ADUtils.HexStringToLdapHexString(stringguid.ToString());
if (ldapHexGuid == null)
{
if (throwOnFail)
throw new ArgumentException(SR.StoreCtxGuidIdentityClaimBadFormat);
else
return false;
}
sb.Append("(objectGuid=");
sb.Append(ldapHexGuid);
sb.Append(')');
break;
case UrnScheme.DistinguishedNameScheme:
sb.Append("(distinguishedName=");
sb.Append(ADUtils.EscapeRFC2254SpecialChars(identity));
sb.Append(')');
break;
case UrnScheme.SidScheme:
if (!SecurityIdentityClaimConverterHelper(identity, false, sb, throwOnFail))
{
return false;
}
break;
case UrnScheme.SamAccountScheme:
int index = identity.IndexOf('\\');
if (index == identity.Length - 1)
if (throwOnFail)
throw new ArgumentException(SR.StoreCtxNT4IdentityClaimWrongForm);
else
return false;
string samAccountName = (index != -1) ? identity.Substring(index + 1) : // +1 to skip the '/'
identity;
sb.Append("(samAccountName=");
sb.Append(ADUtils.EscapeRFC2254SpecialChars(samAccountName));
sb.Append(')');
break;
case UrnScheme.NameScheme:
sb.Append("(name=");
sb.Append(ADUtils.EscapeRFC2254SpecialChars(identity));
sb.Append(')');
break;
case UrnScheme.UpnScheme:
sb.Append("(userPrincipalName=");
sb.Append(ADUtils.EscapeRFC2254SpecialChars(identity));
sb.Append(')');
break;
default:
if (throwOnFail)
throw new ArgumentException(SR.StoreCtxUnsupportedIdentityClaimForQuery);
else
return false;
}
filter = sb.ToString();
return true;
}
protected static string IdentityClaimConverter(FilterBase filter, string suggestedAdProperty)
{
IdentityClaim ic = (IdentityClaim)filter.Value;
if (ic.UrnScheme == null)
throw new ArgumentException(SR.StoreCtxIdentityClaimMustHaveScheme);
string urnValue = ic.UrnValue ?? "";
string filterString = null;
IdentityClaimToFilter(urnValue, ic.UrnScheme, ref filterString, true);
return filterString;
}
// If useSidHistory == false, build a filter for objectSid.
// If useSidHistory == true, build a filter for objectSid and sidHistory.
protected static unsafe bool SecurityIdentityClaimConverterHelper(string urnValue, bool useSidHistory, StringBuilder filter, bool throwOnFail)
{
// String is in SDDL format. Translate it to ldap hex format
void* pSid = null;
byte[] sidB = null;
try
{
if (Interop.Advapi32.ConvertStringSidToSid(urnValue, out pSid) != Interop.BOOL.FALSE)
{
// Now we convert the native SID to a byte[] SID
sidB = Utils.ConvertNativeSidToByteArray((IntPtr)pSid);
if (null == sidB)
{
if (throwOnFail)
throw new ArgumentException(SR.StoreCtxSecurityIdentityClaimBadFormat);
else
return false;
}
}
else
{
if (throwOnFail)
throw new ArgumentException(SR.StoreCtxSecurityIdentityClaimBadFormat);
else
return false;
}
}
finally
{
if (pSid is not null)
Interop.Kernel32.LocalFree(pSid);
}
StringBuilder stringizedBinarySid = new StringBuilder();
foreach (byte b in sidB)
{
stringizedBinarySid.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
string ldapHexSid = ADUtils.HexStringToLdapHexString(stringizedBinarySid.ToString());
if (ldapHexSid == null)
return false;
if (useSidHistory)
{
filter.Append("(|(objectSid=");
filter.Append(ldapHexSid);
filter.Append(")(sidHistory=");
filter.Append(ldapHexSid);
filter.Append("))");
}
else
{
filter.Append("(objectSid=");
filter.Append(ldapHexSid);
filter.Append(')');
}
return true;
}
protected static string CertificateConverter(FilterBase filter, string suggestedAdProperty)
{
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate =
(System.Security.Cryptography.X509Certificates.X509Certificate2)filter.Value;
byte[] rawCertificate = certificate.RawData;
return $"(userCertificate={ADUtils.EscapeBinaryValue(rawCertificate)})";
}
protected static string UserAccountControlConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(string.Equals(suggestedAdProperty, "userAccountControl", StringComparison.OrdinalIgnoreCase));
string result = "";
// bitwise-AND
bool value = (bool)filter.Value;
switch (filter.PropertyName)
{
case AuthPrincEnabledFilter.PropertyNameStatic:
// UF_ACCOUNTDISABLE
// Note that the logic is inverted on this one. We expose "Enabled",
// but AD stores it as "Disabled".
result = value ?
"(!(userAccountControl:1.2.840.113556.1.4.803:=2))" :
"(userAccountControl:1.2.840.113556.1.4.803:=2)";
break;
case SmartcardLogonRequiredFilter.PropertyNameStatic:
// UF_SMARTCARD_REQUIRED
result = value ?
"(userAccountControl:1.2.840.113556.1.4.803:=262144)" :
"(!(userAccountControl:1.2.840.113556.1.4.803:=262144))";
break;
case DelegationPermittedFilter.PropertyNameStatic:
// UF_NOT_DELEGATED
// Note that the logic is inverted on this one. That's because we expose
// "delegation allowed", but AD represents it as the inverse, "delegation NOT allowed"
result = value ?
"(!(userAccountControl:1.2.840.113556.1.4.803:=1048576))" :
"(userAccountControl:1.2.840.113556.1.4.803:=1048576)";
break;
case PasswordNotRequiredFilter.PropertyNameStatic:
// UF_PASSWD_NOTREQD
result = value ?
"(userAccountControl:1.2.840.113556.1.4.803:=32)" :
"(!(userAccountControl:1.2.840.113556.1.4.803:=32))";
break;
case PasswordNeverExpiresFilter.PropertyNameStatic:
// UF_DONT_EXPIRE_PASSWD
result = value ?
"(userAccountControl:1.2.840.113556.1.4.803:=65536)" :
"(!(userAccountControl:1.2.840.113556.1.4.803:=65536))";
break;
case CannotChangePasswordFilter.PropertyNameStatic:
// UF_PASSWD_CANT_CHANGE
// This bit doesn't work correctly in AD (AD models the "user can't change password"
// setting as special ACEs in the ntSecurityDescriptor).
throw new InvalidOperationException(
SR.Format(
SR.StoreCtxUnsupportedPropertyForQuery,
PropertyNamesExternal.GetExternalForm(filter.PropertyName)));
case AllowReversiblePasswordEncryptionFilter.PropertyNameStatic:
// UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
result = value ?
"(userAccountControl:1.2.840.113556.1.4.803:=128)" :
"(!(userAccountControl:1.2.840.113556.1.4.803:=128))";
break;
default:
Debug.Fail("ADStoreCtx.UserAccountControlConverter: fell off end looking for " + filter.PropertyName);
break;
}
return result;
}
protected static string BinaryConverter(FilterBase filter, string suggestedAdProperty)
{
return filter.Value != null ?
$"({suggestedAdProperty}={ADUtils.EscapeBinaryValue((byte[])filter.Value)})" :
$"(!({suggestedAdProperty}=*)))";
}
protected static string ExpirationDateConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(string.Equals(suggestedAdProperty, "accountExpires", StringComparison.OrdinalIgnoreCase));
Debug.Assert(filter is ExpirationDateFilter);
Nullable<DateTime> date = (Nullable<DateTime>)filter.Value;
return !date.HasValue ?
"(|(accountExpires=9223372036854775807)(accountExpires=0))" : // Both values are used to represent "no expiration date set"
$"(accountExpires={ADUtils.DateTimeToADString(date.Value)})";
}
protected static string GuidConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(string.Equals(suggestedAdProperty, "objectGuid", StringComparison.OrdinalIgnoreCase));
Debug.Assert(filter is GuidFilter);
Nullable<Guid> guid = (Nullable<Guid>)filter.Value;
string result = "";
if (guid != null)
{
// Transform from hex string ("1AFF") to LDAP hex string ("\1A\FF")
string ldapHexGuid = ADUtils.HexStringToLdapHexString(guid.ToString());
if (ldapHexGuid == null)
throw new InvalidOperationException(SR.StoreCtxGuidIdentityClaimBadFormat);
result = $"(objectGuid={ldapHexGuid})";
}
return result;
}
protected static string MatchingIntConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(filter.Value is QbeMatchType);
QbeMatchType qmt = (QbeMatchType)filter.Value;
return (ExtensionTypeConverter(suggestedAdProperty, qmt.Value.GetType(), qmt.Value, qmt.Match));
}
protected static string DefaultValutMatchingDateTimeConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(filter.Value is QbeMatchType);
QbeMatchType qmt = (QbeMatchType)filter.Value;
Debug.Assert(qmt.Value is DateTime);
return (DateTimeFilterBuilder(suggestedAdProperty, (DateTime)qmt.Value, LdapConstants.defaultUtcTime, false, qmt.Match));
}
protected static string MatchingDateTimeConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(filter.Value is QbeMatchType);
QbeMatchType qmt = (QbeMatchType)filter.Value;
Debug.Assert(qmt.Value is DateTime);
return (ExtensionTypeConverter(suggestedAdProperty, qmt.Value.GetType(), qmt.Value, qmt.Match));
}
protected static string LastLogonConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(filter.Value is QbeMatchType);
QbeMatchType qmt = (QbeMatchType)filter.Value;
Debug.Assert(qmt.Value is DateTime);
Debug.Assert((suggestedAdProperty == "lastLogon") || (suggestedAdProperty == "lastLogonTimestamp"));
return
"(|" +
DateTimeFilterBuilder("lastLogon", (DateTime)qmt.Value, LdapConstants.defaultUtcTime, false, qmt.Match) +
DateTimeFilterBuilder("lastLogonTimestamp", (DateTime)qmt.Value, LdapConstants.defaultUtcTime, true, qmt.Match) +
")";
}
protected static string GroupTypeConverter(FilterBase filter, string suggestedAdProperty)
{
Debug.Assert(string.Equals(suggestedAdProperty, "groupType", StringComparison.OrdinalIgnoreCase));
Debug.Assert(filter is GroupIsSecurityGroupFilter || filter is GroupScopeFilter);
// 1.2.840.113556.1.4.803 is like a bit-wise AND operator
switch (filter.PropertyName)
{
case GroupIsSecurityGroupFilter.PropertyNameStatic:
bool value = (bool)filter.Value;
// GROUP_TYPE_SECURITY_ENABLED
// If group is enabled, it IS security-enabled
if (value)
return "(groupType:1.2.840.113556.1.4.803:=2147483648)";
else
return "(!(groupType:1.2.840.113556.1.4.803:=2147483648))";
case GroupScopeFilter.PropertyNameStatic:
GroupScope value2 = (GroupScope)filter.Value;
switch (value2)
{
case GroupScope.Local:
// GROUP_TYPE_RESOURCE_GROUP, a.k.a. ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP
return "(groupType:1.2.840.113556.1.4.803:=4)";
case GroupScope.Global:
// GROUP_TYPE_ACCOUNT_GROUP, a.k.a. ADS_GROUP_TYPE_GLOBAL_GROUP
return "(groupType:1.2.840.113556.1.4.803:=2)";
default:
// GROUP_TYPE_UNIVERSAL_GROUP, a.k.a. ADS_GROUP_TYPE_UNIVERSAL_GROUP
Debug.Assert(value2 == GroupScope.Universal);
return "(groupType:1.2.840.113556.1.4.803:=8)";
}
default:
Debug.Fail("ADStoreCtx.GroupTypeConverter: fell off end looking for " + filter.PropertyName);
return "";
}
}
public static string DateTimeFilterBuilder(string attributeName, DateTime searchValue, DateTime defaultValue, bool requirePresence, MatchType mt)
{
string ldapSearchValue = null;
string ldapDefaultValue = null;
bool defaultNeeded = false;
ldapSearchValue = ADUtils.DateTimeToADString(searchValue);
ldapDefaultValue = ADUtils.DateTimeToADString(defaultValue);
StringBuilder ldapFilter = new StringBuilder("(");
if (mt != MatchType.Equals && mt != MatchType.NotEquals)
{
defaultNeeded = true;
}
if (defaultNeeded || (mt == MatchType.NotEquals && requirePresence))
{
ldapFilter.Append("&(");
}
switch (mt)
{
case MatchType.Equals:
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapSearchValue);
break;
case MatchType.NotEquals:
ldapFilter.Append("!(");
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapSearchValue);
ldapFilter.Append(')');
break;
case MatchType.GreaterThanOrEquals:
ldapFilter.Append(attributeName);
ldapFilter.Append(">=");
ldapFilter.Append(ldapSearchValue);
break;
case MatchType.LessThanOrEquals:
ldapFilter.Append(attributeName);
ldapFilter.Append("<=");
ldapFilter.Append(ldapSearchValue);
break;
case MatchType.GreaterThan:
ldapFilter.Append('&');
// Greater-than-or-equals (or less-than-or-equals))
ldapFilter.Append('(');
ldapFilter.Append(attributeName);
ldapFilter.Append(mt == MatchType.GreaterThan ? ">=" : "<=");
ldapFilter.Append(ldapSearchValue);
ldapFilter.Append(')');
// And not-equal
ldapFilter.Append("(!(");
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapSearchValue);
ldapFilter.Append("))");
// And exists (need to include because of tristate LDAP logic)
ldapFilter.Append('(');
ldapFilter.Append(attributeName);
ldapFilter.Append("=*)");
break;
case MatchType.LessThan:
goto case MatchType.GreaterThan;
}
ldapFilter.Append(')');
bool closeFilter = false;
if (defaultNeeded)
{
ldapFilter.Append("(!");
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapDefaultValue);
ldapFilter.Append(')');
closeFilter = true;
}
if (mt == MatchType.NotEquals && requirePresence)
{
ldapFilter.Append('(');
ldapFilter.Append(attributeName);
ldapFilter.Append("=*)");
closeFilter = true;
}
if (closeFilter)
ldapFilter.Append(')');
return (ldapFilter.ToString());
}
public static string ExtensionTypeConverter(string attributeName, Type type, object value, MatchType mt)
{
StringBuilder ldapFilter = new StringBuilder("(");
string ldapValue;
if (typeof(bool) == type)
{
ldapValue = ((bool)value ? "TRUE" : "FALSE");
}
else if (type is ICollection)
{
StringBuilder collectionFilter = new StringBuilder();
ICollection collection = (ICollection)value;
foreach (object o in collection)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionTypeConverter collection filter type " + o.GetType().ToString());
collectionFilter.Append(ExtensionTypeConverter(attributeName, o.GetType(), o, mt));
}
return collectionFilter.ToString();
}
else if (typeof(DateTime) == type)
{
ldapValue = ADUtils.DateTimeToADString((DateTime)value);
}
else
{
ldapValue = ADUtils.PAPIQueryToLdapQueryString(value.ToString());
}
switch (mt)
{
case MatchType.Equals:
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapValue);
break;
case MatchType.NotEquals:
ldapFilter.Append("!(");
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapValue);
ldapFilter.Append(')');
break;
case MatchType.GreaterThanOrEquals:
ldapFilter.Append(attributeName);
ldapFilter.Append(">=");
ldapFilter.Append(ldapValue);
break;
case MatchType.LessThanOrEquals:
ldapFilter.Append(attributeName);
ldapFilter.Append("<=");
ldapFilter.Append(ldapValue);
break;
case MatchType.GreaterThan:
ldapFilter.Append('&');
// Greater-than-or-equals (or less-than-or-equals))
ldapFilter.Append('(');
ldapFilter.Append(attributeName);
ldapFilter.Append(mt == MatchType.GreaterThan ? ">=" : "<=");
ldapFilter.Append(ldapValue);
ldapFilter.Append(')');
// And not-equal
ldapFilter.Append("(!(");
ldapFilter.Append(attributeName);
ldapFilter.Append('=');
ldapFilter.Append(ldapValue);
ldapFilter.Append("))");
// And exists (need to include because of tristate LDAP logic)
ldapFilter.Append('(');
ldapFilter.Append(attributeName);
ldapFilter.Append("=*)");
break;
case MatchType.LessThan:
goto case MatchType.GreaterThan;
}
ldapFilter.Append(')');
return ldapFilter.ToString();
}
protected static string ExtensionCacheConverter(FilterBase filter, string suggestedAdProperty)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter ");
StringBuilder query = new StringBuilder();
if (filter.Value != null)
{
ExtensionCache ec = (ExtensionCache)filter.Value;
foreach (KeyValuePair<string, ExtensionCacheValue> kvp in ec.properties)
{
Type type = kvp.Value.Type ?? kvp.Value.Value.GetType();
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter filter type " + type.ToString());
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter match type " + kvp.Value.MatchType.ToString());
if (kvp.Value.Value is ICollection)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter encountered collection.");
ICollection collection = (ICollection)kvp.Value.Value;
foreach (object o in collection)
{
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter collection filter type " + o.GetType().ToString());
query.Append(ExtensionTypeConverter(kvp.Key, o.GetType(), o, kvp.Value.MatchType));
}
}
else
{
query.Append(ExtensionTypeConverter(kvp.Key, type, kvp.Value.Value, kvp.Value.MatchType));
}
}
}
GlobalDebug.WriteLineIf(GlobalDebug.Info, "ADStoreCtx", "ExtensionCacheConverter complete built filter " + query.ToString());
return query.ToString();
}
///
/// <summary>
/// Adds the specified Property set to the TypeToPropListMap data structure.
/// </summary>
///
private void AddPropertySetToTypePropListMap(Type principalType, StringCollection propertySet)
{
lock (TypeToLdapPropListMap)
{
TypeToLdapPropListMap[MappingTableIndex].TryAdd(principalType, propertySet);
}
}
}
}
|