TLS 1.2 works with LdapConnection but not DirectoryEntry - c#

I am unable to connect to Active Directory with TLS 1.2 using the DirectoryService class. I am able to connect using TLS 1.2 via LDP on Windows, Open Source LDAPAdmin on Windows and LdapConnection in a .Net 4.7.2 console application. I have verified the TLS 1.2 connections using WireShark. Here is some sample code:
static void Main(string[] args)
{
LdapConnection conn = new LdapConnection("server.domain.com:636");
var op = conn.SessionOptions;
op.ProtocolVersion = 3;
op.SecureSocketLayer = true;
op.VerifyServerCertificate = (ldapConnection, serverCertificate) =>
{
return true;
};
conn.AuthType = AuthType.Negotiate;
var cred = new NetworkCredential("user#domain.com", "password");
conn.Credential = cred;
conn.Bind(cred);
Console.WriteLine("LdapConnection Success");
// Is not TLS 1.2
var de = new DirectoryEntry("LDAP://server.domain.com", "user#domain.com", "password", AuthenticationTypes.Secure);
try
{
foreach (var child in de.Children)
{
Console.WriteLine(child);
}
Console.WriteLine($"{de.Path} Success");
}
catch (Exception ex)
{
Console.WriteLine($"{de.Path} {ex.Message}");
}
//Does not work
de = new DirectoryEntry("LDAP://server.domain.com:636", "user#domain.com", "password");
try
{
foreach (var child in de.Children)
{
Console.WriteLine(child);
}
Console.WriteLine($"{de.Path} Success");
}
catch (Exception ex)
{
Console.WriteLine($"{de.Path} {ex.Message}");
}
//Does not work
de = new DirectoryEntry("LDAP://server.domain.com", "user#domain.com", "password", AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.Secure);
try
{
foreach (var child in de.Children)
{
Console.WriteLine(child);
}
Console.WriteLine($"{de.Path} Success");
}
catch (Exception ex)
{
Console.WriteLine($"{de.Path} {ex.Message}");
}
//Does not work
de = new DirectoryEntry("LDAP://server.domain.com:636", "user#domain.com", "password", AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.Secure);
try
{
foreach (var child in de.Children)
{
Console.WriteLine(child);
}
Console.WriteLine($"{de.Path} Success");
}
catch (Exception ex)
{
Console.WriteLine($"{de.Path} {ex.Message}");
}
Console.ReadKey();
}
Any Idea's how to connect through the DirectoryService class? I have seem many questions about this topic in StackOverflow which is why I included all of the other answers I read about in the sample code.

The correct way to connect via LDAPS with DirectoryEntry is:
e = new DirectoryEntry("LDAP://server.domain.com:636", "user#domain.com", "password");
You don't need to specify AuthenticationTypes.SecureSocketsLayer, since that's the only possible way to connect on port 636, but it also doesn't hurt anything if you do.
My guess is that you have a problem with the certificate. Probably one of two reasons:
The domain name on the certificate does not match the domain name you are using to connect. For example, if you are connecting using the domain name server.domain.com, then the certificate must be issued to (or have a Subject Alternative Name for) server.domain.com. If the domain on the certificate is just domain.com, or just server, then it does not match and it will fail.
The certificate is not issued by an authority that the client computer trusts. It could be self-signed, for example.
It works with LdapConnection because you have this:
op.VerifyServerCertificate = (ldapConnection, serverCertificate) =>
{
return true;
};
That's your custom code for validating the certificate, which always returns true. So any issue with the cert will be ignored. DirectoryEntry does not give you a way to do that.
You can use the PowerShell code in this answer to download the certificate (make sure to change the domain name) and inspect it. Windows will flag an issue with it (if there is one) when you double-click the .cer file.

Related

LDAPs query for cross domains in AD forest trust

I have two domains(DomainA and DomainB) each in their own AD forests and I have established a forest trust between them,. I am now trying to connect and bind to DomainA and query DomainA for certain attributes of a DomainB user, which is basically a cross domain query.
I am using DirectoryServices.Protocols.LdapConnection and I have set the port to be 636(SSL port) and set the Referral Chasing to ReferralChasingOption.All in C# code. Yet, when the code executes to search the response, I see this error message -
"A referral was returned by the server"
private static LdapConnection getLdapsConnection()
{
LdapConnection connection;
LdapDirectoryIdentifier identifier;
NetworkCredential credential;
try
{
credential = getAdminCredential();
identifier = new LdapDirectoryIdentifier("DomainA.local", 636);
connection = new LdapConnection(identifier, credential)
{
AuthType = AuthType.Basic,
SessionOptions =
{
ProtocolVersion = 3,
SecureSocketLayer = true,
VerifyServerCertificate = new VerifyServerCertificateCallback(true)
}
};
connection.SessionOptions.ReferralChasing = ReferralChasingOptions.All;
return connection;
}
catch (Exception exc)
{
return null;
}
}
public static SearchResponse retrieveUserAttributes(string userName(DomainB_User), List<string> attributesToReturn, LdapConnection connObj)
{
try
{
String filter = "(&(objectCategory=person)(sAMAccountName=" + userName + "))";
String target = "CN=userName,CN=Users,DC=DomainB,DC=local";
SearchRequest searchRequest = new SearchRequest(target, filter, System.DirectoryServices.Protocols.SearchScope.Subtree, attributesToReturn.ToArray());
SearchResponse response = (SearchResponse)connObj.SendRequest(searchRequest);
return response;
}
catch (Exception exc)
{
return null;
}
}
Not sure why the referral error is seen even though referral chasing option is set.
Note:
The same code works perfectly fine for LDAP over 389, this is an issue only for LDAP over ssl for cross domain

How to check Sharepoint connectivity before ClientContext.ExecuteQuery

I am very new to sharepoint. What is the fastest and most efficient way to check Sharepoint site connectivity before ClientContext.ExecuteQuery.
ClientContext ctx = new ClientContext(ConfigurationManager.AppSettings["sharepoint siteUrl"]);
float pageLoadTime = getPageLoadTime(ctx);
if(pageLoadTime > 0.5)
{
MessageBox.Show("Sharepoint site is not available!");
return;
}
//do very heavy query
....
ctx.ExecuteQuery();
There is no such standard method to do that. But you can achieve this like following code
using (ClientContext sourceContext = new ClientContext("Sharepoint Url"))
{
try
{
sourceContext.ExecuteQuery();
List list = sourceContext.Web.Lists.GetByTitle("Test");
ListItemCollection itemColl = list.GetItems(CamlQuery.CreateAllItemsQuery());
sourceContext.Load(itemColl);
sourceContext.ExecuteQuery();
}
catch (System.Net.WebException ex)
{
if (ex.Message == "The remote server returned an error: (404) Not Found.")
{
Console.WriteLine("SharePoint not available");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
because "ExecuteQuery" is the method which connect to SharePoint through Client.svc wcf service.
Hope this helps...

Prevent ODP.net connetion from being open with expired password

I'm working on a Silverlight application that uses oracle security to authenticate the users. (This is a business requirement so it can't be changed).
I do so by calling a WCF web service that attempts to open a connection to the database using the provided username and password. If the connection fails, I catch the exception and return a message to the user, here's the login code:
[OperationContract]
public LoginResult LogIn(string username, string password, DateTime preventCache)
{
var result = new List<string>();
try
{
connectionString = ConfigurationManager.ConnectionStrings["SecurityBD"].ToString();
connectionString = connectionString.Replace("[username]", username);
connectionString = connectionString.Replace("[password]",passowrd)
using (var connection = new Oracle.DataAccess.Client.OracleConnection())
{
connection.ConnectionString = connectionString;
connection.Open();
if (connection.State == System.Data.ConnectionState.Open)
{
connection.Close();
return new LoginResult(true, GetPermisos(username), preventCache);
}
else
{
return new LoginResult(false, null, preventCache);
}
}
}
catch (Oracle.DataAccess.Client.OracleException ex)
{
if (ex.Number == 1017)
{
return new LoginResult(new SecurityError("Wrong credentials.", ErrorType.InvalidCredentials));
}
//Password expired.
if (ex.Number == 28001)
{
return new LoginResult(new SecurityError("Password expired.", ErrorType.PasswordExpired));
}
//Acount is locked.
if (ex.Number == 28000)
{
return new LoginResult(new SecurityError("Account is locked.", ErrorType.AccountLocked));
}
else
{
return new LoginResult(new SecurityError("An error occurred while attempting to connect." + Environment.NewLine + "Error: " + ex.ToString(), ErrorType.UndefinedError));
}
}
catch (Exception exg)
{
return new LoginResult(new SecurityError("An error occurred while attempting to connect." + Environment.NewLine + "Error: " + exg.ToString(), ErrorType.UndefinedError));
}
}
If the connection fails because of an expired password, I show the corresponding message to the user and then prompt him for his old and new password, and then send the new credentials to a ChangePassword method on my web serivce.
[OperationContract]
public ChangePasswordResult ChangePassword(string username, string oldPasswrod, string newPassword)
{
string connectionString = string.Empty;
try
{
connectionString = ConfigurationManager.ConnectionStrings["SecurityBD"].ToString();
connectionString = connectionString.Replace("[username]", username);
connectionString = connectionString.Replace("[password]",passowrd)
using (var connection = new OracleConnection(connectionString))
{
connection.Open();
if (connection.State == System.Data.ConnectionState.Open)
{
connection.Close();
using (var newConnection = new Oracle.DataAccess.Client.OracleConnection(connectionString))
{
newConnection.OpenWithNewPassword(Cryptography.TransportDecrypt(newPassword));
if (newConnection.State == System.Data.ConnectionState.Open)
{
return new ChangePasswordResult(null);
}
}
}
return new ChangePasswordResult(new SecurityError("Couldn't connect to the database.", ErrorType.UndefinedError));
}
}
catch (OracleException ex)
{
if (ex.Number == 1017)
{
return new ChangePasswordResult(new SecurityError("Wrong password", ErrorType.InvalidCredentials));
}
//Password expired.
if (ex.Number == 28001)
{
using (var newConnection = new Oracle.DataAccess.Client.OracleConnection(connectionString))
{
try
{
newConnection.OpenWithNewPassword(Cryptography.TransportDecrypt(newPassword));
if (newConnection.State == System.Data.ConnectionState.Open)
{
return new ChangePasswordResult(null);
}
else
{
return new ChangePasswordResult(new SecurityError("No se pudo establecer una conexión con la base de datos", ErrorType.UndefinedError));
}
}
catch (Oracle.DataAccess.Client.OracleException oex)
{
if (oex.Number == 28003)
return new ChangePasswordResult(new SecurityError("You'r new password does not match the security requeriments.." + Environment.NewLine + oex.Message, ErrorType.PasswordNotChanged));
else
return new ChangePasswordResult(new SecurityError(oex.Message, ErrorType.UndefinedError));
}
}
}
//Acount is locked.
if (ex.Number == 28000)
{
return new ChangePasswordResult(new SecurityError("Account is locked.", ErrorType.AccountLocked));
}
else
{
return new ChangePasswordResult(new SecurityError("Couldn't establish a connection." + Environment.NewLine + "Error: " + ex.Message, ErrorType.UndefinedError));
}
}
catch
{
throw;
}
}
After I perform the change password operation, the user is still able to connect with the old password and he's not able to connect with the new password. Only after I restart the application the change seems to take effect.
I'm using oracle's ODP.net driver. With Microsoft's oracle client, the user is able to connect with both the new and the old password after the password change.
The preventCache parameter was there only to verify that there was no type of client cache. I send the current date from the client, and then return the same value from the web service to see if it actually changes with subsequent requests, and it does as expected.
I've tried listening to the InfoMessage event of the connection, to see if there's any warning, but doing this prevents the password expired exception from being risen, and the code never reaches the eventHandler.
I'm completely lost, this behavior seems very odd to me and I still haven't figured out the root cause for the problem.
I've tryied copying the LogIn and ChangePassword methods on a desktop (WPF) application and it behaves exactly the same. So i guess the problem is not in the silverlight client.
Ok, i've figured this out. Checking with Toad the connection reminded opend even after executing the Connection.Close() method. This behavior seems to be part of the connection pooling mechanism from oracle.
Including Pooling=false on the connection string solved the problem.

Add a new User for the database in mongodb

I want to add a new user to newly created database and if this user exists then i will connect to that database.
My code is:
public CreateDatabaseOperationResult CreateDatabase(string databaseName,string username,string password, MongoServer server)
{
CreateDatabaseOperationResult createDatabaseOpResult = new CreateDatabaseOperationResult();
string message = null;
MongoCredentials credentials = new MongoCredentials(username, password);
MongoUser user = new MongoUser(credentials, false);
try
{
if (IsDatabaseNameValid(databaseName, out message))
{
if (server.DatabaseExists(databaseName, admincredentials) == true)
{
createDatabaseOpResult.Database = server.GetDatabase(databaseName, credentials);
MongoUser tempuser = createDatabaseOpResult.Database.FindUser(username);
if (tempuser.Equals(user))
{
//createDatabaseOpResult.DatabaseExists = true;
createDatabaseOpResult.IsOperationSuccessfull = false;
throw new ArgumentException("Database Already exist with different set of credentials ");
}
}
else
{
createDatabaseOpResult.Database = server.GetDatabase(databaseName, credentials);
createDatabaseOpResult.Database.AddUser(user);
//createDatabaseOpResult.DatabaseExists = false;
}
createDatabaseOpResult.IsOperationSuccessfull = true;
}
}
catch (MongoQueryException ex)
{
createDatabaseOpResult.Error = ex;
}
//catch (MongoAuthenticationException ex)
//{
// createDatabaseOpResult.Error = ex;
//}
catch (MongoException ex)
{
createDatabaseOpResult.Error = ex;
}
catch (ArgumentException ex)
{
createDatabaseOpResult.Error = ex;
}
return createDatabaseOpResult;
}
When i use the existing database it connects to that database but when i try to add new use Database.AddUser gives error 'invalid credentials for this database'
Please see the error and reply
Most people use the mongo shell to add and remove users, but if you really want to do it in C# the trick is to use the right credentials depending on what you are trying to do. Assume you have the following two sets of credentials, one for the admin database and one for regular databases:
var adminCredentials = new MongoCredentials("myadminusername", "myadminpassword", true);
var userCredentials = new MongoCredentials("myusername", "myuserpassword");
Note that when creating the adminCredentials you must pass true to the admin parameter.
To test if a database exists requires admin credentials:
if (server.DatabaseExists("mydatabase", adminCredentials))
{
// database exists
}
To add a user requires admin credentials:
var myDatabaseWithAdminCredentials = server.GetDatabase("mydatabase", adminCredentials);
if (myDatabaseWithAdminCredentials.FindUser("myusername") == null)
{
myDatabaseWithAdminCredentials.AddUser(userCredentials);
}
Normally you use regular user credentials to work with a database:
var myDatabaseWithUserCredentials = server.GetDatabase("mydatabase", userCredentials);
var count = myDatabaseWithUserCredentials.GetCollection("mycollection").Count();
Also, keep in mind that each database can have any number of users, so you don't really need to be checking whether the database already exists with a different set of credentials.

Remove user account from administrators group on remote machine using C# and AccountManagment namespace

I have the code:
public bool RemoveUserFromAdministratorsGroup(UserPrincipal oUserPrincipal, string computer)
{
try
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine, computer, null, ContextOptions.Negotiate, _sServiceUser, _sServicePassword);
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, "Administrators");
oGroupPrincipal.Members.Remove(oUserPrincipal);
oGroupPrincipal.Save();
return true;
}
catch
{
return false;
}
}
It is worked without any excaption. But when i run my app again i see this user in my listview. So, the user wasn't removed.
I have solved the issue without AccountManagment namespace.
public bool RemoveUserFromAdminGroup(string computerName, string user)
{
try
{
var de = new DirectoryEntry("WinNT://" + computerName);
var objGroup = de.Children.Find(Settings.AdministratorsGroup, "group");
foreach (object member in (IEnumerable)objGroup.Invoke("Members"))
{
using (var memberEntry = new DirectoryEntry(member))
if (memberEntry.Name == user)
objGroup.Invoke("Remove", new[] {memberEntry.Path});
}
objGroup.CommitChanges();
objGroup.Dispose();
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
}
The below solution is for deleting the user with the help of Directory Service ...
using System.DirectoryServices
private DeleteUserFromActiveDirectory(DataRow in_Gebruiker)
{
DirectoryEntry AD = new DirectoryEntry(strPathActiveDirectory ,
strUsername, strPassword)
DirectoryEntry NewUser =
AD.Children.Find("CN=TheUserName", "User");
AD.Children.Remove(NewUser);
AD.CommitChanges();
AD.Close();
}
I don't know what is exactly your problem but coding this way :
try
{
PrincipalContext context = new PrincipalContext(ContextType.Domain, "WM2008R2ENT:389", "dc=dom,dc=fr", "jpb", "passwd");
/* Retreive a user principal
*/
UserPrincipal user = UserPrincipal.FindByIdentity(context, "user1");
/* Retreive a group principal
*/
GroupPrincipal adminGroup = GroupPrincipal.FindByIdentity(context, #"dom\Administrateurs");
foreach (Principal p in adminGroup.Members)
{
Console.WriteLine(p.Name);
}
adminGroup.Members.Remove(user);
adminGroup.Save();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Give me the following exception :
Information about the domain could not be retrieved (1355)
Digging a bit arround that show me that I was running my code on a computer that was not on the target domain. When I run the same code from the server itself it works. It seems that the machine running this code must at least contact the DNS of the target domain.

Categories

Resources