Read all users from AD using Novell.Directory.Ldap.NETStandard - c#

I need to read all users from the AD. Here is code that I am using:
using Novell.Directory.Ldap;
using Novell.Directory.Ldap.Controls;
using System.Linq;
namespace LdapTestApp
{
class Program
{
static void Main()
{
LdapConnection ldapConn = new LdapConnection();
ldapConn.SecureSocketLayer = true;
ldapConn.Connect(HOST, PORT);
try
{
var cntRead = 0;
int? cntTotal = null;
var curPage = 0;
ldapConn.Bind(USERNAME, PASSWORD);
do
{
var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl[]
{
new LdapSortControl(new LdapSortKey("sn"), true),
new LdapVirtualListControl("sn=*", 0, 10)
});
ILdapSearchResults searchResults = ldapConn.Search(
"OU=All Users,DC=homecredit,DC=ru",
LdapConnection.ScopeSub,
"(&(objectCategory=person)(objectClass=user))",
null,
false,
constraints
);
while (searchResults.HasMore() && ((cntTotal == null) || (cntRead < cntTotal)))
{
++cntRead;
try
{
LdapEntry entry = searchResults.Next();
}
catch (LdapReferralException)
{
continue;
}
}
++curPage;
cntTotal = GetTotalCount(searchResults as LdapSearchResults);
} while ((cntTotal != null) && (cntRead < cntTotal));
}
finally
{
ldapConn.Disconnect();
}
}
private static int? GetTotalCount(LdapSearchResults results)
{
if (results.ResponseControls != null)
{
var r = (from c in results.ResponseControls
let d = c as LdapVirtualListResponse
where (d != null)
select (LdapVirtualListResponse)c).SingleOrDefault();
if (r != null)
{
return r.ContentCount;
}
}
return null;
}
}
}
I used this question Page LDAP query against AD in .NET Core using Novell LDAP as basis.
Unfortunatelly I get this exception when I am trying to recieve the very first entry:
"Unavailable Critical Extension"
000020EF: SvcErr: DSID-03140594, problem 5010 (UNAVAIL_EXTENSION), data 0
What am I doing wrong?

VLVs are browsing indexes and are not directly related to the possibility or not to browse large numbers of entries (see generic documentation). So even if this control would be activated on your AD, you wouldn't be able to retrieve more than 1000 elements this way :
how VLVs work on AD
MaxPageSize is 1000 by default on AD (see documentation)
So what you can do:
use a specific paged results control, but it seems that the Novell C# LDAP library does not have one
ask you the question: "is this pertinent to look for all the users in a single request?" (your request looks like a batch request: remember that a LDAP server is not designed for the same purposes than a classic database - that can easily return millions of entries - and that's why most of LDAP directories have default size limits around 1000).
The answer is no: review your design, be more specific in your LDAP search filter, your search base, etc.
The answer is yes:
you have a single AD server: ask your administrator to change the MaxPageSize value, but this setting is global and can lead to several side effects (ie. what happens if everybody start to request all the users all the time?)
you have several AD servers: you can configure one for specific "batch like" queries like the one you're trying to do (so large MaxPageSize, large timeouts etc.)

I had to use approach described here:
https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard/issues/71#issuecomment-420917269
The solution is far from being perfect but at least I am able to move on.

Starting with version 3.5 the library supports Simple Paged Results Control - https://ldapwiki.com/wiki/Simple%20Paged%20Results%20Control - and the usage is as simple as ldapConnection.SearchUsingSimplePaging(searchOptions, pageSize) or ldapConnection.SearchUsingSimplePaging(ldapEntryConverter, searchOptions, pageSize) - see Github repo for more details - https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard and more specifically use the tests as usage samples.

Related

Sub domain as query string

Is there any way in ASP.net C# to treat sub-domain as query string?
I mean if the user typed london.example.com then I can read that he is after london data and run a query based on that. example.com does not currently have any sub-domains.
This is a DNS problem more than an C#/ASP.Net/IIS problem. In theory, you could use a wildcard DNS record. In practice, you run into this problem from the link:
The exact rules for when a wild card will match are specified in RFC 1034, but the rules are neither intuitive nor clearly specified. This has resulted in incompatible implementations and unexpected results when they are used.
So you can try it, but it's not likely to end well. Moreover, you can fiddle with things until it works in your testing environment, but that won't be able to guarantee things go well for the general public. You'll likely do much better choosing a good DNS provider with an API, and writing code to use the API to keep individual DNS entries in sync. You can also set up your own public DNS server, though I strongly recommend using a well-known and reputable commercial DNS host.
An additional problem you can run into is the TLS/SSL certificate (because of course you're gonna use HTTPS. Right? RIGHT!?) You can try a wild card certificate and probably be okay, but depending on what else you do you may find it's not adequate; suddenly you're needing to provision a separate SSL certificate for every city entry in your database, and that can be a real pain, even via the Let's Encrypt service.
If you do try it, IIS is easily capable of mapping the requests to your ASP.Net app based on a wildcard host name, and ASP.Net itself is easily capable of reading and parsing the host name out of the request and returning different results based on that. IIS URL re-writing should be able to help with this, though I'm not sure whether you can do stock MVC routing in C#/ASP.Net based on this attribute.
I have to add to the previous answers, that after you fix the dns, and translate the subdomain to some parameters you can use the RewritePath to move that parameters to your pages.
For example let say that a function PathTranslate(), translate the london.example.com to example.com/default.aspx?Town=1
Then you use the RewritePath to keep the sub-domain and at the same time send your parameters to your page.
string sThePathToReWrite = PathTranslate();
if (sThePathToReWrite != null){
HttpContext.Current.RewritePath(sThePathToReWrite, false);
}
string PathTranslate()
{
string sCurrentPath = HttpContext.Current.Request.Path;
string sCurrentHost = HttpContext.Current.Request.Url.Host;
//... lot of code ...
return strTranslatedUrl
}
A low tech solution can be like this: (reference: https://www.pavey.me/2016/03/aspnet-c-extracting-parts-of-url.html)
public static List<string> SubDomains(this HttpRequest Request)
{
// variables
string[] requestArray = Request.Host().Split(".".ToCharArray());
var subDomains = new List<string>();
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return subDomains;
}
// make sure we have all the parts necessary
if (requestArray == null)
{
return subDomains;
}
// last part is the tld (e.g. .com)
// second to last part is the domain (e.g. mydomain)
// the remaining parts are the sub-domain(s)
if (requestArray.Length > 2)
{
for (int i = 0; i <= requestArray.Length - 3; i++)
{
subDomains.Add(requestArray[i]);
}
}
// return
return subDomains;
}
// e.g. www
public static string SubDomain(this HttpRequest Request)
{
if (Request.SubDomains().Count > 0)
{
// handle cases where multiple sub-domains (e.g. dev.www)
return Request.SubDomains().Last();
}
else
{
// handle cases where no sub-domains
return string.Empty;
}
}
// e.g. azurewebsites.net
public static string Domain(this HttpRequest Request)
{
// variables
string[] requestArray = Request.Host().Split(".".ToCharArray());
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return string.Empty;
}
// special case for localhost
if (Request.IsLocalHost())
{
return Request.Host().ToLower();
}
// make sure we have all the parts necessary
if (requestArray == null)
{
return string.Empty;
}
// make sure we have all the parts necessary
if (requestArray.Length > 1)
{
return $"{requestArray[requestArray.Length - 2]}.{requestArray[requestArray.Length - 1]}";
}
// return empty string
return string.Empty;
}
Following question is similar to yours:
Using the subdomain as a parameter

How to get All attributes from an Active Directory user in C#

I have been searching for quite some time for a solution using C# code that can query an Active Directory user for all the attributes it has registered to it, whether or not they have a NULL Value. These attributes are visible through the Attribute editor tab in the properties of the user in ADSI Edit on the domain server.
AD user attributes in ADSI edit
I need to dynamically retrieve these attributes, which means I probably can't reliably get these attribute names through the ADSI documentation on MSDN and because not all of these attributes might be user object specific:
https://msdn.microsoft.com/en-us/library/ms675090(v=vs.85).aspx
Here is what I have tried so far, but only got a fraction of the attributes of the user object:
PS command Get-ADUser -Identity administrator -Properties: This retrieved a good part of the attributes, but not nearly all of them and I do not know what .NET Classes and methods are invoked during this command, since TypeName = Microsoft.ActiveDirectory.Management.ADUser, which does not exist in the .NET framework. How can I see the specific methods that are using from .NET in PS?
C# calling this method:
public bool GetUserAttributes(out List<string> userAttributes, string userName)
{
userAttributes = new List<string>();
var valueReturn = false;
try
{
const string pathNameDomain = "LDAP://test.local";
var directoryEntry = new DirectoryEntry(pathNameDomain);
var directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = "(&(objectClass=user)(sAMAccountName=" + userName + "))"
};
var searchResults = directorySearcher.FindAll();
valueReturn = searchResults.Count > 0;
StreamWriter writer = new StreamWriter("C:\\LDAPGETUSERADEXAMPLE.txt");
foreach (SearchResult searchResult in searchResults)
{
foreach (var valueCollection in searchResult.Properties.PropertyNames)
{
userAttributes.Add(valueCollection.ToString() + " = " + searchResult.Properties[valueCollection.ToString()][0].ToString());
try
{
writer.WriteLine("Bruger attribut:" + valueCollection);
}
catch (Exception)
{
throw;
}
}
}
C# calling this method:
public List<string> GetADUserAttributes()
{
string objectDn = "CN=testuser,OU=TEST,DC=test,DC=local";
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
List<string> attributes = new List<string>();
foreach (string attribute in objRootDSE.Properties.PropertyNames)
{
attributes.Add(attribute);
}
return attributes;
}
What should I do to not filter out any attributes of the user object I am trying to retrieve from?
I am aware that Active Directory by default will only shows attributes that are default or have a value in them, I am trying to overcome this limitation.
EDIT 1:
I have temporarily postponed the specific question.
I have been trying to benchmark which of these methods are the fastest at retrieving (READ Operation) the SAM account name of 10.000 individual AD users called for example "testuser", the methods I benchmark are the following:
Time to complete: about 500 msec : ADSI - system.directoryservices
Time to complete: about 2700 msec: Principal - searcher system.directoryservices.accountmanagement
Time to complete: about NOT WORKING :LDAP - System.DirectoryServices.Protocols
Time to complete: about 60 msec : SQL - System.Data.SqlClient
I am querying for the user information from a workstation - Windows 10 machine in the domain I am querying. the workstation (4 vcpu), DC (2vpu) and DB (2vcpu) server is run as Hyper V vm's.
All attributes that any class can have are defined in Active Directory Schema
Use this to query for the user class. Then just call GetAllProperties method
var context = new DirectoryContext(DirectoryContextType.Forest, "amber.local");
using (var schema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetSchema(context))
{
var userClass = schema.FindClass("user");
foreach (ActiveDirectorySchemaProperty property in userClass.GetAllProperties())
{
// property.Name is what you're looking for
}
}
However AD schema may vary from one AD environment to another. For example, third party programs or Exchange Server may extend schema with custom attributes. It means that the solution with pre-defined columns will work only for a specific environment.

TFS API - How to get a Team's Adminstrator?

I'm trying to programmatically retrieve a Team's Administrator Users.
For example in a setup like in the picture how can I get 'Billy' as being the Administrator of Team 'QC Manager'?
I already have the code that gets all users in a Team via IIdentityManagementService's ListApplicationGroups, getting the group using FirstOrDefault ... and then getting its users via ReadIdentities.
I've done some poking around in the Team Web Access assemblies, and this seems to be only available on the server side at the moment (no Rest or Client Object Model option available). The code looks like this:
TeamFoundationIdentity identity;
string token = service.GetSecurableToken(requestContext, teamIdentity.Descriptor, out identity);
AccessControlList list = requestContext.GetService<SecurityService>().QueryAccessControlLists(requestContext, FrameworkSecurity.IdentitiesNamespaceId, token, null, false, false).FirstOrDefault<AccessControlList>();
List<IdentityDescriptor> list2 = new List<IdentityDescriptor>();
if (list != null)
{
foreach (AccessControlEntry entry in list.AccessControlEntries)
{
if ((entry.Allow & 8) == 8)
{
list2.Add(entry.Descriptor);
}
}
}
return service.ReadIdentities(requestContext, list2.ToArray());
Where GetSecurableToken looks like:
internal static string CreateSecurityToken(TeamFoundationIdentity group)
{
return (group.GetAttribute(IdentityAttributeTags.LocalScopeId, string.Empty) + FrameworkSecurity.IdentitySecurityPathSeparator + group.TeamFoundationId.ToString());
}
From here you should be able to piece together the code to read and writes these lists. To go poking around yourself, look for the Microsoft.TeamFoundation.Server.Core.dll, Class Microsoft.TeamFoundation.Server.Core.TeamFoundationTeamService to be specific.
If you're able to rewrite it into something useful I'd be grateful and might stick it into the TfsTeamTools, at the moment I don't have much time on my hands to pick this up.
Found this post TFS11 API: Managing Team Administrators; I duplicate code for easy reference, see original post for complete info.
static void Main(string[] args)
{
// Connect to the TFS server and get the team project URI.
var collection = GetServer("server_uri");
var projectUri = GetProjectUri(collection, "project_name");
// Retrieve the default team.
TfsTeamService teamService = collection.GetService<TfsTeamService>();
TeamFoundationTeam defaultTeam = teamService.GetDefaultTeam(projectUri, null);
// Get security namespace for the project collection.
ISecurityService securityService = collection.GetService<ISecurityService>();
SecurityNamespace securityNamespace = securityService.GetSecurityNamespace(FrameworkSecurity.IdentitiesNamespaceId);
// Use reflection to retrieve a security token for the team.
MethodInfo mi = typeof(IdentityHelper).GetMethod("CreateSecurityToken", BindingFlags.Static | BindingFlags.NonPublic);
string token = mi.Invoke(null, new object[] { defaultTeam.Identity }) as string;
// Retrieve an ACL object for all the team members.
var allMembers = defaultTeam.GetMembers(collection, MembershipQuery.Expanded).Where(m => !m.IsContainer);
AccessControlList acl = securityNamespace.QueryAccessControlList(token, allMembers.Select(m => m.Descriptor), true);
// Retrieve the team administrator SIDs by querying the ACL entries.
var entries = acl.AccessControlEntries;
var admins = entries.Where(e => (e.Allow & 15) == 15).Select(e => e.Descriptor.Identifier);
// Finally, retrieve the actual TeamFoundationIdentity objects from the SIDs.
var adminIdentities = allMembers.Where(m => admins.Contains(m.Descriptor.Identifier));
}

Asp.Net web forms log in schema - Keep a list of logged in users

i'm a beginner at developing a whole system so, need some approach guidance here folks. Hope you can help me! =)
In a try to create a log in schema, i have a Control class which keeps a list of logged users.
public static class ControleAcesso
{
private static List<Associado> associadosLogados = new List<Associado>();
public static Mensagens doLogin(Page pagina, String login, String senha)
{
Mensagens retorno = new Mensagens();
retorno = AcessoDAL.Login(login, senha);
if (retorno.Erros.Count() <= 0 && retorno.Objeto != null)
{
Associado assocLogado = new Associado();
Associados assocEncontrado = (Associados)retorno.Objeto;
assocLogado.ID = assocEncontrado.Associado;
assocLogado.Nome = assocEncontrado.Nome;
assocLogado.Nome_Fantasia = assocEncontrado.Nome_Fantasia;
assocLogado.Data_Inclusao = assocEncontrado.Data_Inclusao;
assocLogado.Email = assocEncontrado.Email;
assocLogado.Data_Alteracao = assocEncontrado.Data_Alteracao;
assocLogado.Login = assocEncontrado.Login;
assocLogado.Senha = assocEncontrado.Senha;
assocLogado.CGC_CPF = assocEncontrado.CGC_CPF;
assocLogado.SessionID = pagina.Session.SessionID;
var associadoJaLogado = associadosLogados.Where(x => x.ID == assocLogado.ID).FirstOrDefault();
if (associadoJaLogado != null)
{
pagina.Session.Remove(associadoJaLogado.SessionID);
associadosLogados.Remove(associadoJaLogado);
}
associadosLogados.Add(assocLogado);
}
return retorno;
}
}
So, this method basicaly do the login call to a Data Access Layer class. If the log in returns a user, i do Add this user to my list.
So, to later know my logged in users and retrieve data about then just using my session ID, i do some management in the list. Just the basic. Removing the logged in one and adding the new one.
The problem, as you probably noticed, is that, when i have two requests for the same credentials at the "same" time, it would allow the user to log in two times.
So, is this the best practice for log in schemes? Would you guys suggest me to change something?
I'm using ASP.NET Web Forms with C#.
Thank you in advice.
Unless you explicitly want to force your users to use your application with one device at a time, you should not worry about the possibility that they might log in. So this is a problem if and only if it can violate one of your terms.

changing webservice calls to test or prod via radio button

I have created a vsto application that calls a webservice. everything seems to work just fine, but i would like to extend the functionality to call the test service version of my production service.
code snippet that works that calls my test service.
//how do i change here to be dynamic?
npfunctions.finfunctions service = new npfunctions.finfunctions();
var Results = service.ValidateFoapal(index.ToArray(), fund.ToArray(), org.ToArray(), prog.ToArray(), acct.ToArray(), row.ToArray());
/* if their are no error then return a "Y" for success.*/
if (Results.Count() < 0) { return LocallErrorInd; }
/*well we have encountered errors lets adjust the spreadsheet to notify the user.*/
else{
//REMOVE ANY VISUAL ERRORS
Microsoft.Office.Interop.Excel.Range delRng = Globals.ThisAddIn.Application.Range["R:S"];
delRng.Delete(XlDeleteShiftDirection.xlShiftToLeft);
for (int i = 0; i < Results.Count(); i++)
{//set the error indicator
LocallErrorInd = "Y";
//account error:
if (Results[i].FVALJOR_FUND_WARNING == "Y")
{
Microsoft.Office.Interop.Excel.Range WrkRng = Globals.ThisAddIn.Application.Range[Results[i].FVALJOR_ROW];
WrkRng.Offset[0, 17].Value2 = "Invalid Account";
}
i have seen this post How can I dynamically switch web service addresses in .NET without a recompile?
but it requires me to change my config file i would really like to change the service variable to point to another location based on a variable and basically flip from prod to test on my command. as i see it right now it appears that i would have to duplicate the code but i know there has got to be a better way. i like it to be something like.
if (TestBtn.Checked == true)
{
npfunctions.finfunctions service = new npfunctions.finfunctions();
Results = service.ValidateFoapal(index.ToArray(), fund.ToArray(), org.ToArray(), prog.ToArray(), acct.ToArray(), row.ToArray());
}
if (PrdBtn.Checked == true)
{
prdFunctions.finfunctions service = new prdFunctions.finfunctions();
Results = service.ValidateFoapal(index.ToArray(), fund.ToArray(), org.ToArray(), prog.ToArray(), acct.ToArray(), row.ToArray());
}
/* if their are no error then return a "Y" for success.*/
if (Results.Count() < 0) { return LocallErrorInd; }
Does your service object not have a URL property?
2nd option, you can use a config file transformation so you do not need to manually change the settings(after the intial setup of course).
npfunctions.finfunctions service = new npfunctions.finfunctions();
if (TestBtn.Checked == true)
{
service.url="<testurl>";
}
else
{
service.url="<produrl>";
}
Results = service.ValidateFoapal(index.ToArray(), fund.ToArray(), org.ToArray(), prog.ToArray(), acct.ToArray(), row.ToArray());
In our test client we have a drop-down to select between dev or tst. We also have buttons to select proxy or net.tcp. (We have many different people using our service using different methods).
In the app.config the names of the endpoints correlate with different selectable options.
Eg. name="BasicHttpBinding_IInterface_PROXY_DEV"
You can then dynamically build up which endpoint you would like to use and go with that.

Categories

Resources