I'm working on an internal portal that allows users to request accounts to various other domains we manage.
I have to check if a certain account already exists via LDAP, so this is what I do to make a connection.
const string server = "ldap.mydomain.net:636";
using (var ldapSSLConn = new LdapConnection(server))
{
var networkCredential = new NetworkCredential("user", "supersecurepassword");
ldapSSLConn.SessionOptions.SecureSocketLayer = true;
ldapSSLConn.AuthType = AuthType.Basic;
ldapSSLConn.SessionOptions.SecureSocketLayer = true;
ldapSSLConn.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback((con, cer) => true);
ldapSSLConn.Bind(networkCredential);
// Search happens here
// Return results
}
I can then use the ldapSSLConn to search for existing accounts.
Everytime I need to make a connection, it takes me +/- 20 seconds, the search itself 85ms.
Is there a way I can cache the connection? For example open it on Application_Start(), and then reference it when I need it?
I found a solution that works for me.
I'm checking if a connection is cached now, with;
if (System.Web.HttpContext.Current.Cache["LDAPConnection"] == null)
If it isn't, I make a connection (see question), and at the end, cache it:
System.Web.HttpContext.Current.Cache.Add("LDAPConnection", ldapSSLConn, null, DateTime.Now.AddHours(8), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, null);
Related
I'm trying to execute the binding to an LDAP instance using .NET Objects.
Sorry but this is the first time I fight against this kind of enemy (and hope it will be the last one as well!).
This is what I actually do:
LdapDirectoryIdentifier serverId = new LdapDirectoryIdentifier(primaryIP, securePort);
NetworkCredential credentials = new NetworkCredential(username, password);
using (LdapConnection conn = new LdapConnection(serverId, credentials))
{
try
{
//conn.SessionOptions.ProtocolVersion = 3;
conn.SessionOptions.SecureSocketLayer = true;
conn.AuthType = (AuthType)authType;
conn.Bind();
Console.WriteLine("OK!!");
}
catch (LdapException lex)
{
Console.WriteLine($"Errore {lex.ErrorCode}: {lex.Message}");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
Where:
primaryIP is the name of LDAP instance
securePort is 636
username and password are absolutely correct (I've checked them logging in into the intranet)
I've found many examples, and everything seems pretty plain and simple. Anyway I can't make through it.
Tried also with all the AuthTypes available, with no luck.
As said, the user exists because I've been able to log into different apps that use this kind of authentication.
Ok, got it on my own.
The username must be set with the full DN.
Now it works correctly.
I have been looking into how these two new settings will effect with our c# code that connects to an ldap server and performs user lookups
Using the code below to connect to an AD i have found a few scenarios that no longer work when these settings are switched on.
private static LdapConnection ConnectAndBind(
string server,
int port,
int timeout,
string userName,
string pwd,
AuthType authType,
bool useSSL,
bool useV3)
{
var con = new LdapConnection(new LdapDirectoryIdentifier(server, port));
if (useSSL)
{
con.SessionOptions.SecureSocketLayer = useSSL;
con.SessionOptions.VerifyServerCertificate = VerifyServerCertificate;
con.SessionOptions.QueryClientCertificate = QueryClientCertificate;
}
con.Timeout = new TimeSpan(0, 0, timeout);
con.SessionOptions.ProtocolVersion = useV3 ? 3 : 2;
try
{
con.AuthType = authType;
con.Credential = new NetworkCredential(userName, pwd);
con.Bind();
}
catch (Exception e)
{
throw new ProviderException(
ProviderException.ErrorIdentifier.AuthenticationFailed,
LanguageLogic.GetString("AuthenticationProvider.ConnectError"),
e.Message);
}
return con;
}
This is used in the context of a webforms/mvc asp.net (4.5) app once connected its used to import user details in the the app
but at the moment depending on how the registry keys for the two settings on the AD server are set i am finding some situations where it does not connect (the error returned is that the supplied credentials are invalid)
The first two tables are kinda how i expected it to work with non signed/non ssl basic bind not working
how ever i cannot find a reason why when the Channel binding is set to required (table 3) it does not work for the other 3 red entries
Has any one else been working on this that could shed some light on the matter. would newer version of .net support this setting.
Thanks for any info
UPDATE 1
so i downloaded Softerra LDAP browser. i get the same results using that . so i dont think its my code.
as soon as i turn on the reg key for Channel Binding i get the specified credentials are invalid for those connection methods over SSL
i have updated the AD server with all the latest patches but no change.
Background
I've been experimenting with active directory access in C# to find out how to connect/validate credentials in various ways. At the bottom of this answer I've included some code snippets to give an idea of what I've done, maybe this can be built upon to fulfill my aim.
Main Aim
If I have valid credentials for connecting to an Active Directory, can I take an string representing a username/email address (assuming it exists in the userPrincipalName or similar field), and get back the objectGUID?
Or do I need to take other things into account like: the permissions those credentials have to search other users; knowledge of the structure of different ADs; if userPrincipalName is the correct field to search?
Code Snippets (experimental beginnings, not fully functional for my aim)
var credentials = new NetworkCredential(username, password, hostname);
var serverId = new LdapDirectoryIdentifier(hostname);
var connection = new LdapConnection(serverId, credentials);
try
{
connection.Bind();
}
catch (Exception e)
{
//error
Console.WriteLine(e);
connection.Dispose();
return;
}
//success
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", hostname, baseDn), username, password);
var searcher = new DirectorySearcher(dirEntry)
{
Filter = "(&(&(objectClass=user)(objectClass=person)))"
};
var resultCollection = searcher.FindAll();
searcher.Dispose();
You're on the right track with DirectorySeacher. You just need a proper query, and a few other tweaks.
Modify the Filter so you find what you're looking for.
a. If you have the email address:
(&(objectClass=user)(objectClass=person)(mail=email#example.com))
Or, (&(objectClass=user)(objectClass=person)(proxyAddresses=smtp:email#example.com)) (this will match against secondary email addresses too)
b. If you have a username, it depends which username you have.
User Principal Name: (&(objectClass=user)(objectClass=person)(userPrincipalName=username#example.com))
What is normally called the "username", which is often formatted like DOMAIN\username: (&(objectClass=user)(objectClass=person)(sAMAccountName=myusername))
Use DirectorySeacher.PropertiesToLoad. If you don't, it will retrieve every attribute that has a value, which is just wasted network traffic.
You don't need to dispose the DirectorySearcher, but you do need to dispose resultCollection since the documentation says you can end up with a memory leak if you leave it up to garbage collection.
So, assuming you have the userPrincipalName, you would have something like this:
var userToLookFor = "username#example.com";
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", hostname, baseDn), username, password);
var searcher = new DirectorySearcher(dirEntry)
{
Filter = $"(&(objectClass=user)(objectClass=person)(userPrincipalName={userToLookFor}))",
SizeLimit = 1 //we're only looking for one account
};
searcher.PropertiesToLoad.Add("objectGuid");
using (var resultCollection = searcher.FindAll())
{
if (resultCollection.Count == 1)
{
var userGuid = new Guid((byte[]) resultCollection[0].Properties["objectGuid"][0]);
}
else
{
//the account was not found - do something else
}
}
I've not done any LDAP-based authentication before and also I've not worked with any LDAP server before. So I need a free online LDAP server to play with, I've found this https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
However my code is not working (or the info there has become invalid, I'm not sure), the result of authen is always false, here is my code:
path = "ldap.forumsys.com:389/dc=example,dc=com";
using (var pc = new PrincipalContext(ContextType.Domain, null, path))
{
//this always returns false
var ok = pc.ValidateCredentials("read-only-admin", "password");
}
Could you make it work on your side? Or at least please assert that the info there is invalid, in that case if possible please give me some other info (from other free LDAP servers for testing).
I don't think the server is Active Directory. You can refer to this question for how to connect to a LDAP server in C#.
Second Edit:
Checked with MS people. They also suggest LdapConnection.
https://github.com/dotnet/corefx/issues/31809
Edit:
I can use DirectoryEntry to bind to the server. I am not sure why PrincipalContext does not work, but you can try this way.
Here is a sample code for validating user and password.
Tested on .Net Core 2.1, with System.DirectoryServices package 4.5.0.
using System;
using System.DirectoryServices;
namespace LDAPTest
{
class Program
{
static void Main(string[] args)
{
string ldapServer = "LDAP://ldap.forumsys.com:389/dc=example,dc=com";
string userName = "cn=read-only-admin,dc=example,dc=com";
string password = "password";
var directoryEntry = new DirectoryEntry(ldapServer, userName, password, AuthenticationTypes.ServerBind);
// Bind to server with admin. Real life should use a service user.
object obj = directoryEntry.NativeObject;
if (obj == null)
{
Console.WriteLine("Bind with admin failed!.");
Environment.Exit(1);
}
else
{
Console.WriteLine("Bind with admin succeeded!");
}
// Search for the user first.
DirectorySearcher searcher = new DirectorySearcher(directoryEntry);
searcher.Filter = "(uid=riemann)";
searcher.PropertiesToLoad.Add("*");
SearchResult rc = searcher.FindOne();
// First we should handle user not found.
// To simplify, skip it and try to bind to the user.
DirectoryEntry validator = new DirectoryEntry(ldapServer, "uid=riemann,dc=example,dc=com", password, AuthenticationTypes.ServerBind);
if (validator.NativeObject.Equals(null))
{
Console.WriteLine("Cannot bind to user!");
}
else
{
Console.WriteLine("Bind with user succeeded!");
}
}
}
}
Reference:
https://www.c-sharpcorner.com/forums/ldap-authentication2
I figure it out too, and having no LDAP knowledge I´ve come up with this.
The problem in your solution may be first, you are using "ldap://" instead of "LDAP://", since it was something I came into when coding this. But I use System.DirectoryServices library.
I tested against this magnificent free to test LDAP server
var path = "LDAP://ldap.forumsys.com:389/dc=example,dc=com";
var user = $#"uid={username},dc=example,dc=com";
var pass = "password";
var directoryEntry = new DirectoryEntry(path, user, pass, AuthenticationTypes.None);
var searcher = new DirectorySearcher(directoryEntry);
searcher.PropertiesToLoad.Add("*");
var searchResult = searcher.FindOne();
I don´t understand exactly what all of this lines does, however, and lookign for a solution I found some recommendations.
on the path the "LDAP://" string should be on block mayus.
in the user, sometimes you need to use "cn=username-admin" for validating admins, be sure to also set Authentication type to ServerBind.
It seems as if read-only-admin is not a valid user. Try replacing:
var ok = pc.ValidateCredentials("read-only-admin", "password");
with
var ok = pc.ValidateCredentials("tesla", "password");
If that does not work, the other other issue would be on the LDAP's server side.
A good option regardless is to set up an Amazon Web Services EC2 server (it is free) and load Windows Server onto it. This gives you your own server and you learn how to set up an LDAP server (which is pretty easy).
I am trying to use Autonomy SDK to do some admin operation, But when i try to connect to database i keep getting an exception:
[NRTSession ][TrustedLogin ]Cannot request exclusive semaphores at
interrupt time.
My code is in C#
admin.INRTDMS dms = new admin.NRTDMS();
//INRTSessions sessions = dms.Sessions;
admin.INRTSession session = dms.Sessions.Add("TestServer");
session.TrustedLogin();
Any help is really appreciable.
Check if the user that you are using to connect is enabled in DbAdmin.
Also, you can try this:
public void ConnectToDbAdmin(string server, string user, string pass)
{
NRTDMS nrtDMS = new NRTDMS();
NRTSession nrtSession;
NRTSessions nrtSessions;
nrtSessions = nrtDMS.Sessions;
nrtSessions.Add(server);
nrtSession = nrtSessions.Item(1);
nrtSession.Login(user, pass);
//or
nrtSession.TrustedLogin();
}