Get LDAP TokenGroups using System.DirectoryServices.Protocols in .NET5 - c#

I'm porting some code from System.DirectoryServices to System.DirectoryServices.Protocols due to the need to run on linux.
The majority of the code is fine but this little piece that retrieves tokenGroups isn't coming across.
The original code reads:
using (var ldapObject = new DirectoryEntry(objectPath, username, password, AuthenticationTypes.Secure))
{
ldapObject.Options.Referral = ReferralChasingOption.All;
ldapObject.RefreshCache(new string[] { "tokenGroups" });
foreach (byte[] sid in ldapObject.Properties["tokenGroups"])
{
// Won't run on linux
var groupSID = new System.Security.Principal.SecurityIdentifier(sid, 0).ToString();
try
{
var group = this.GetSecurityGroupBySID(groupSID);
}
catch (Exception ex)
{
log.Warn($"Failed to get group with SID {groupSID}", ex);
}
}
}
Where objectPath is of the form $"LDAP://{domainName}/{distinguishedName}"
My attempt looks like this:
public void GetExpandedGroups(string objectMail, LdapConnection ldapConnection)
{
var usersPath = "OU=active,OU=users,DC=path,DC=etc"; //Not real values
var filter = $"(&(objectClass = user)(!userAccountControl:1.2.840.113556.1.4.803:= 2)(mail={objectMail}))";
var request = new SearchRequest(usersPath, filter, SearchScope.Subtree, new string[] { "tokenGroups" });
request.Controls.Add(new SecurityDescriptorFlagControl(SecurityMasks.Dacl | SecurityMasks.Group | SecurityMasks.Owner));
var ldapObject = (SearchResponse)ldapConnection.SendRequest(request); // Error thrown here
foreach (byte[] sid in ldapObject.Entries[0].Attributes["tokenGroups"])
{
// Do stuff...
}
}
Whenever this runs I get an error, thrown. The stack trace and error response are below:
at System.DirectoryServices.Protocols.LdapConnection.<ConstructResponseAsync>d__57.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)
at LDAPTesting.Connectors.Protocols.GetExpandedGroups(String objectMail, LdapConnection ldapConnection) in C:\code\LDAPTesting\LDAPTesting\Connectors\Protocols.cs:line 62
at LDAPTesting.Connectors.Protocols.GetGroups() in C:\code\LDAPTesting\LDAPTesting\Connectors\Protocols.cs:line 103
at LDAPTesting.Program.Main(String[] args) in C:\code\LDAPTesting\LDAPTesting\Program.cs:line 17
00002120: SvcErr: DSID-031404CC, problem 5012 (DIR_ERROR), data 0
My thoughts are that I'm querying the wrong path but it looks to match the path that the original code requests on so I'm not sure what else I'm missing.

For anyone that comes along this, the solution is incredibly simple.
The SearchRequest object needs to be of the scope Base. Really obvious when reading what the error message. So the line:
var request = new SearchRequest(usersPath, filter, SearchScope.Subtree, new string[] { "tokenGroups" });
Should instead read:
var request = new SearchRequest(usersPath, filter, SearchScope.Base, new string[] { "tokenGroups" });
I had a feeling that there would be an obvious and simple answer due to overlooking something and indeed there was!

I think I found the solution - it's just the first value that is not correct - if that is ignored, everything works fine!

Related

SOAP Service call breaking ignores try catch

I've got a dll that relies on a SOAP call to a web service, and I was provided with the WSDL file from the vendor, and after some testing, I've got code that will successfully call the service and get the response with no issue. However, when the vendor moved the service to a new server, my code starting failing.
Specifically, I get a NullRefernceException that, even though it is square in a try/catch block, ignores all of that and crashes anyway.
my call looks like this:
new ChannelFactory<GRChannel>(binding, address).Using(async(factory) =>
{
factory.Credentials.UserName.UserName = "TestUser";
factory.Credentials.UserName.Password = "1234";
var proxy = factory.CreateChannel();
proxy.Open();
var context = new OperationContext((IClientChannel)proxy);
var prevOpContext = OperationContext.Current;
OperationContext.Current = context;
try
{
var results = await proxy.ZBAPI_GOODSMVT_CREATEAsync(payload); //<-- Null happens here
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(ZBAPI_GOODSMVT_CREATEResponse));
using (StringWriter text = new StringWriter())
{
serializer.Serialize(text, results.ZBAPI_GOODSMVT_CREATEResponse);
Message = text.ToString();
}
}
catch
{
//Nothing here matters, we never make it to this point
}
});
the Stack Trace is this at the time of failure:
at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass1_0.<CreateGenericTask>b__0(IAsyncResult asyncResult)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at RFCDLL.RFC.<>c__DisplayClass5_0.<<RunRequest>b__0>d.MoveNext() in C:\Users\User01\source\repos\RFCDLL\RFCDLL\RFC.cs:line 46
obviously, having a null failure is fine, and expected when the service isn't running, I just want to make sure the error handling works, and I can not have the whole app crash when that happens.

Google.Apis.Auth.OAuth2.Responses.TokenResponseException: invalid_grant, Robot is disabled

I am using Visual Studio for Mac for a .NET Console C# project using Google Speech API.
I am getting an error that says:
Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="Getting metadata from plugin failed with error: Exception occurred in metadata credentials plugin. Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"invalid_grant", Description:"Robot is disabled.", Uri:""
at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.ExecuteAsync(TokenRequest request, HttpClient httpClient, String tokenServerUrl, CancellationToken taskCancellationToken, IClock clock) in C:\Apiary\support1351\Src\Support\Google.Apis.Auth\OAuth2\Requests\TokenRequestExtenstions.cs:line 52
at Google.Apis.Auth.OAuth2.ServiceAccountCredential.RequestAccessTokenAsync(CancellationToken taskCancellationToken) in C:\Apiary\support1351\Src\Support\Google.Apis.Auth\OAuth2\ServiceAccountCredential.cs:line 212
at Google.Apis.Auth.OAuth2.TokenRefreshManager.RefreshTokenAsync() in C:\Apiary\support1351\Src\Support\Google.Apis.Auth\OAuth2\TokenRefreshManager.cs:line 129
at Google.Apis.Auth.OAuth2.TokenRefreshManager.GetAccessTokenForRequestAsync(CancellationToken cancellationToken) in C:\Apiary\support1351\Src\Support\Google.Apis.Auth\OAuth2\TokenRefreshManager.cs:line 114
at Google.Apis.Auth.OAuth2.ServiceAccountCredential.GetAccessTokenForRequestAsync(String authUri, CancellationToken cancellationToken) in C:\Apiary\support1351\Src\Support\Google.Apis.Auth\OAuth2\ServiceAccountCredential.cs:line 235
at Grpc.Auth.GoogleAuthInterceptors.<>c__DisplayClass2_0.<<FromCredential>b__0>d.MoveNext() in T:\src\github\grpc\src\csharp\Grpc.Auth\GoogleAuthInterceptors.cs:line 48
--- End of stack trace from previous location where exception was thrown ---
at Grpc.Core.Internal.NativeMetadataCredentialsPlugin.GetMetadataAsync(AuthInterceptorContext context, IntPtr callbackPtr, IntPtr userDataPtr) in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeMetadataCredentialsPlugin.cs:line 83")'
at the line I mark with the comment "// error here" in the code:
class Program
{
static void Main(string[] args)
{
var URI = "https://speech.googleapis.com/v1/speech:recognize?key=[API_KEY]";
Console.WriteLine("Start!");
AsyncRecognizeGcs(URI);
Console.WriteLine("End.");
}
static object AsyncRecognizeGcs(string storageUri)
{
var speech = SpeechClient.Create();
var longOperation = speech.LongRunningRecognize(new RecognitionConfig()
{
Encoding = RecognitionConfig.Types.AudioEncoding.Flac,
SampleRateHertz = 44100,
AudioChannelCount = 2,
LanguageCode = "en",
}, RecognitionAudio.FromStorageUri(storageUri)); // error here
longOperation = longOperation.PollUntilCompleted();
var response = longOperation.Result;
foreach (var result in response.Results)
{
foreach (var alternative in result.Alternatives)
{
Console.WriteLine($"Transcript: { alternative.Transcript}");
}
}
return 0;
}
}
How do I fix this? Is there something else I need to set up with Google?
Recommend updating to a new version of Google.Cloud.PubSub.V1, which will pull in a newer version of Grpc.Core.
Use the last update of Google.Apis.Auth.
It turns out that I had the wrong credentials json file. Stupid mistake.

Null Reference exception using ADAL for acquiringtokenasync with resource and clientkey [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 5 years ago.
I am hitting this null reference exception using ADAL with version:3.13.1.846, this doesn’t repro every time. Any ideas?
I have made sure that when calling AcquireTokenAsync on the AuthenticationContext as recommended in many MS samples, for example. Here's a link to the docs for that method and the SecureClientSecret which is causing errors.
I am not passing in a null value
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.IdentityModel.Clients.ActiveDirectory.SecureClientSecret.ApplyTo(IDictionary`2 parameters)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<SendTokenRequestAsync>d__65.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<RunAsync>d__55.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.<AcquireTokenForClientCommonAsync>d__49.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.<AcquireTokenAsync>d__26.MoveNext()
Thanks,
Sara
I've had a look over the source code for the SecureClientSecret
SecureClientSecret
I'm a bit confused as to what Microsoft are trying to do with their ApplyTo method, it looks a bit like the kind of pattern you see with TryParse and out params, populating a dictionary by reference.
More explicity, populating the dictionary value at OAuthParameter.ClientSecret ('client_secret'as that translates to), with the decrypted value of the SecureString stashed away inside SecureClientSecret.
Microsoft then clear down the private SecureString member, which means any further calls to ApplyTo are going to be met with an unhandled exception when we hit the first line:
var output = new char[secureString.Length];
'secureString.Length' is going to blow, the exception you'd expect, calling null.Length... System.NullReferenceException, something that we see happening in the stack trace.
If MS really need to clear away that member field after a single use for some reason that I don't understand at the moment, maybe they should consider making the class more robust.
Add an IsApplied method/property to the interface:
public interface ISecureClientSecret
{
/// <summary>
/// Writes SecureString to the dictionary.
/// </summary>
/// <param name="parameters"></param>
void ApplyTo(IDictionary<string, string> parameters);
/// <summary>
/// Indicates whether this <see cref="ISecureClientSecret"/> has already been applied.
/// </summary>
/// <returns><c>true</c> if applied, otherwise <c>false</c></returns>
bool IsApplied { get; };
}
Which could easily be implemented in the concrete class with:
public bool IsApplied => secureString == null;
Also, the ApplyTo method could make use of locking to prevent bad things happening with lots of calls:
public void ApplyTo(IDictionary<string, string> parameters)
{
secureLock.WaitOne();
// We don't want to blow up if ApplyTo is called multiple times.
if (IsApplied)
return;
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.SecureStringToCoTaskMemUnicode(secureString);
parameters[OAuthParameter.ClientSecret] = Marshal.PtrToStringUni(ptr);
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.ZeroFreeCoTaskMemUnicode(ptr);
if (secureString != null && !secureString.IsReadOnly())
secureString.Clear();
secureString = null;
secureLock.Set();
}
}
Sticking a AutoResetEvent in as a member for the class.
private AutoResetEvent secureLock = new AutoResetEvent(true);
It would be great if one of the devs on this project could explain what they're trying to do as things seem a little convoluted as a newbie looking in.
UPDATE
As #Nan Yu previously said, I'd like to see the poster's calling code for the delegate that provides the auth. After playing around with my own version I'm wondering if making sure the vars related to the SecureClientSecret are scoped as low down as possible would get around the potential issues I discussed above.
I.e. Don't have any member/field references to the SecureClientSecret. Don't have any vars scoped outside the delegate itself either, as I think the underlying SecureString inside the SecureClientSecret object gets nulled and things don't work out well when you try to get another token passing in the original SecureString.
I believe this is essentially what #'Kevin ssssssss' was suggesting in their answer!
var delegateAuthProvider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenant}/oauth2/token");
var result = await authContext.AcquireTokenAsync(Resource, new ClientCredential(appClientId, new SecureClientSecret(appSecretSecure)));
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
});
MS samples seem to recommend calling for a new token every time before making a Graph request, which personally seems like a waste to me, also if you end up making a lot of async calls (i.e await Task.WhenAll(someTasks);) you might end up with a race-like condition where every task wants to call for a token, and you'll get a lot of open sockets as the tasks get scheduled. Here's a snippet of some code I'd use when trying to deal with the token/delegate etc.
namespace GraphTestConsole
{
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security;
using System.Threading.Tasks;
public class Program
{
private const string Resource = "https://graph.microsoft.com";
private const string AppClientId = "YOUR_CLIENT_ID";
private static readonly SecureString AppClientSecretSecure = ToSecureString("YOUR_CLIENT_SECRET");
private const string Tenant = "YOUR_TENANT.onmicrosoft.com";
private static IGraphServiceClient _graphClient;
private static string _token;
private static DateTimeOffset _tokenExpiresOn = DateTime.Now;
private static readonly AsyncLock _mutex = new AsyncLock();
public static void Main(string[] args)
{
AsyncContext.Run(() => AsyncMain(AppClientId, AppClientSecretSecure, Tenant));
}
private static async Task AsyncMain(string appId, SecureString appSecretSecure, string tenant)
{
try
{
var upn = "YOUR_USER_UPN";
_graphClient = GetGraphClient(appId, appSecretSecure, tenant);
var user = await _graphClient.Users[upn].Request().Select("id").GetAsync();
}
catch (Exception ex)
{
// Handle the exception...
Console.WriteLine(DateTime.Now.ToString());
Console.WriteLine(ex);
}
}
private static bool IsTokenInvalid => string.IsNullOrEmpty(_token) || (_tokenExpiresOn - DateTime.Now).TotalSeconds <= 0;
private static GraphServiceClient GetGraphClient(string appClientId, SecureString appSecretSecure, string tenant)
{
var delegateAuthProvider = new DelegateAuthenticationProvider(async (requestMessage) =>
{
using (await _mutex.LockAsync())
{
if (IsTokenInvalid)
{
var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenant}/oauth2/token");
var result = await authContext.AcquireTokenAsync(Resource, new ClientCredential(appClientId, new SecureClientSecret(appSecretSecure)));
_token = result.AccessToken;
_tokenExpiresOn = result.ExpiresOn;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _token);
}
});
return new GraphServiceClient(delegateAuthProvider);
}
private static SecureString ToSecureString(IEnumerable<char> value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var secured = new SecureString();
var charArray = value.ToArray();
for (int i = 0; i < charArray.Length; i++)
{
secured.AppendChar(charArray[i]);
}
secured.MakeReadOnly();
return secured;
}
}
}
P.S. I still think the MS ADAL code is poorly written. :)
It seems the SecureClientSecret class is intended to be single-use -- so if you construct a new one for every request, this problem should go away.

Serialize SendGridMessage object

I am building a system that need to send some transactional mails, and to achieve this I am using Azure storage queues to store the message temporarily before it is picked up by a WebJob and sent off to the intended recipient.
My Code is as follows:
SendGridMessage message = new SendGridMessage();
//Populate message with details - omitted for brevity
var serializer = new JavaScriptSerializer();
var modelAsString = serializer.Serialize(message);
try
{
var setting = CloudConfigurationManager.GetSetting("AzureStorageConnectionString");
var account = CloudStorageAccount.Parse(setting);
var queueClient = account.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("FSPortalEmailQueue");
queue.CreateIfNotExists();
queue.AddMessage(new CloudQueueMessage(modelAsString));
}
catch (Exception ex)
{
//Something went wrong
}
Each time I try to execute the coder, an exception is thrown on the
var modelAsString = serializer.Serialize(message);
"Exception has been thrown by the target of an invocation."
The inner exception thrown was
{"Bad key path!"} from source "SendGrid.SmtpApi"
Please advise what I am doing wrong here.
After a bit more digging, it turns out that the message.header node was not being initialised. After adding
message.Header = new SendGrid.SmtpApi.Header();
message.Header.SetTo(new List<String> { enquiry.EnquiryCreatedBy.Email });
all started working pretty magically

LdapConnection SearchRequest throws exception for "The size limit was exceeded"

Because of the fact that we are required to connect to an LDAP server using LDAPS we must use LdapConnection instead of DirectoryEntry.
Here is the source code:
SearchResponse response;
using (LdapConnection con = new LdapConnection(new LdapDirectoryIdentifier(Host, Port)))
{
if (IsSSL)
{
con.SessionOptions.SecureSocketLayer = true;
con.SessionOptions.VerifyServerCertificate =
(connection, certificate)
=> true;
}
con.Credential = new NetworkCredential(_username, _password);
con.AuthType = AuthType.Basic;
con.Bind();
if (logMessage != null)
logMessage("Connected to LDAP");
string sFilter = String.Format(
"(&(objectcategory=person)(objectclass=user){0}(!(userAccountControl:1.2.840.113556.1.4.803:=2)))",
filter
);
SearchRequest request = new SearchRequest("OU=Corp,DC=mydc,DC=com", sFilter, SearchScope.Subtree);
request.Attributes.Add(Resources.objectguid);
request.Attributes.Add(Resources.givenname);
request.Attributes.Add(Resources.sn);
request.Attributes.Add(Resources.initials);
request.Attributes.Add(Resources.samaccountname);
request.Attributes.Add(Resources.userprincipalname);
request.Attributes.Add(Resources.mail);
request.Attributes.Add(Resources.objectsid);
request.Attributes.Add(Resources.department);
request.Attributes.Add(Resources.company);
request.SizeLimit = 10;
response = (SearchResponse) con.SendRequest(request);
}
Upon execution of the source code (we have verified credentials, host, port, etc - using an external 3rd party software) we get the following exception:
The size limit was exceeded
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.DirectoryServices.Protocols.DirectoryOperationException: The size limit was exceeded
Source Error:
response = (SearchResponse) con.SendRequest(request);
[DirectoryOperationException: The size limit was exceeded]
System.DirectoryServices.Protocols.LdapConnection.ConstructResponse(Int32
messageId, LdapOperation operation, ResultAll resultType, TimeSpan
requestTimeOut, Boolean exceptionOnTimeOut) +2385
System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest
request, TimeSpan requestTimeout) +499
System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest
request) +50
UserSearchProvider.ADUserSearchProvider.QueryStore(UserSearchCriteriaCollection
criterias, Action1 logMessage) in c:\Users\stemarie\Documents\Visual
Studio
2012\Projects\Idealink.Modules\UserSearchProvider\UserSearchProvider\ADUserSearchProvider.cs:298
UserSearchProvider.UserSearchProvider.QueryAndSort(UserSearchCriteriaCollection
criterias, Action1 logMessage) in c:\Users\stemarie\Documents\Visual
Studio
2012\Projects\Idealink.Modules\UserSearchProvider\UserSearchProvider\UserSearchProvider.cs:77
UserSearchProvider.UserSearchProvider.Search(UserSearchCriteriaCollection
criterias, Action1 logMessage) in c:\Users\stemarie\Documents\Visual
Studio
2012\Projects\Idealink.Modules\UserSearchProvider\UserSearchProvider\UserSearchProvider.cs:33
UserSearchProvider.UserSearchService.Search(UserSearchCriteriaCollection
criterias, Action1 logMessage) in c:\Users\stemarie\Documents\Visual
Studio
2012\Projects\Idealink.Modules\UserSearchProvider\UserSearchProvider\UserSearchService.cs:44
UserSearchProviderTest._Default.Page_Load(Object sender, EventArgs e) in c:\Users\stemarie\Documents\Visual Studio
2012\Projects\Idealink.Modules\UserSearchProvider\UserSearchProviderTest\Default.aspx.cs:28
The part that confuses me is that we did specify the maximum size limit, we don't want more than 100 entries - we want to limit it. But yet the library consistently throws the error even if we specify a SizeLimit of 1.
Does anyone have any insights/suggestions regarding this issue? We are very close to getting this working and just need to resolve this last problem.
You should use cookies in a function similar to this.
The function returns a collection of SearchResponse objects, which the caller should loop though.
private List<SearchResponse> SearchDirectory(string distinguishedName, string searchFilter, System.DirectoryServices.Protocols.SearchScope searchScope, params string[] attributeList)
{
List<SearchResponse> result = new List<SearchResponse>();
SearchResponse response = null;
int maxResultsToRequest = 100;
try
{
PageResultRequestControl pageRequestControl = new PageResultRequestControl(maxResultsToRequest);
// used to retrieve the cookie to send for the subsequent request
PageResultResponseControl pageResponseControl;
SearchRequest searchRequest = new SearchRequest(distinguishedName, searchFilter, searchScope, attributeList);
searchRequest.Controls.Add(pageRequestControl);
while (true)
{
response = (SearchResponse)connection.SendRequest(searchRequest);
result.Add(response);
pageResponseControl = (PageResultResponseControl)response.Controls[0];
if (pageResponseControl.Cookie.Length == 0)
break;
pageRequestControl.Cookie = pageResponseControl.Cookie;
}
}
catch (Exception e)
{
// do something with the error
}
return result;
}
As it turns out, this works:
try
{
response = (SearchResponse)con.SendRequest(request);
return response.Entries.Cast<SearchResultEntry>()
.Select(entry => entry.Attributes)
.Select(x => GetADUserSearchItemFromADProperties(x, logMessage))
.Where(user => user.HasName)
.Cast<IUserSearchItem>();
}
catch (DirectoryOperationException ex)
{
response = (SearchResponse) ex.Response;
return response.Entries.Cast<SearchResultEntry>()
.Select(entry => entry.Attributes)
.Select(x => GetADUserSearchItemFromADProperties(x, logMessage))
.Where(user => user.HasName)
.Cast<IUserSearchItem>();
}
The MSDN documentation states that you get a DirectoryResponse class as the DirectoryOperationException.Response property. You can however cast this property to a SearchResponse type and then use the SearchResponse.Entries property to get the entries that you have received prior to hitting the specified SizeLimit.
I have tried this and I get the expected results, I'm just a bit upset that I have to work with an exception in order to perform the operation.

Categories

Resources