Validate User via remote active directory - c#

I have a strange scenario that I hope you guys can help with, I need to validate the current logged in user with active directory, this isn't a problem if they are on the network but in some instances they will be on another network (visiting clients) and in order for them to use the software they need to validate against AD.
At present I am using the following code am I correct in saying this will work locally and remotely? If not how can I get it to validate credentials?
DomainServer = new ActiveDirectory(Microsoft.Exchange.WebServices.Data.ExchangeVersion.Exchange2010, "LDAP://DOMAIN.NAME", "https://exchange.domain.name/ews/exchange.asmx");
DomainServer.connect();
if (!DomainServer.isConnected())
{
domain_errors = "Unable to connect to Active Directory.";
}
class ActiveDirectory
{
private ExchangeService _ExchangeServer;
private DirectoryEntry _searchRoot;
private DirectorySearcher _search;
private SearchResult _searchresult;
private ExchangeVersion _ExchangeVer;
private string _ActiveDirectoryAddress;
private string _ActiveDirectoryURL;
public ActiveDirectory(ExchangeVersion Ver, string ActiveDirectoryAddress, string ActiveDirectoryURL)
{
_ActiveDirectoryURL = ActiveDirectoryURL;
_ActiveDirectoryAddress = ActiveDirectoryAddress;
_ExchangeVer = Ver;
}
public void connect()
{
_ExchangeServer = new ExchangeService(_ExchangeVer);
_ExchangeServer.UseDefaultCredentials = true;
_ExchangeServer.Url = new Uri(_ActiveDirectoryURL);
_ExchangeServer.Timeout = 60;
}
public bool isConnected()
{
if (_searchRoot.Properties.Count > 0){
return true;
} else {
return false;
}
}
}

Windows caches usernames and passwords on local machines so that a domain user can login even if the machine, such as a laptop, is not connected to the domain. Since Windows is essentially handling the authentication for you, all you really need to do is authorize who can use the software. I can think of a few possibilities.
First, in the database, you could maintain a user table with the SIDs and usernames of those users with access to the software. When a user executes the program, check that the SID of the currently logged in user is in that table. This way, you can restrict what users can actually execute the software without connecting to Active Directory. This would also allow you to revoke access at the database level.
Secondly, does the database server have access to the Active Directory? If so, create a group in AD for users with access to this system, then grant that group access in the database. Set the database connection to use Windows authentication. Therefore, if the person is in that group, they have access to the database. Otherwise, they do not. Then, you can control access just by adding or removing them from that group and security will be controlled at the database level.
Third (and I'm not a fan of this one), if you have a web server, run a web service (using HTTPS of course) that accepts a username and password and then use that web service to connect through your firewall and authenticate against AD. Then, just return a result to your application. This presents some security concerns with passing credentials to a web service and opening connections through the firewall.

Related

C#: Check If User Has Access To Server Without Attempting Connection

Good afternoon,
I am currently facing an issue where I have a list of server names, and I need to verify that a windows authenticated user has permissions to the specified server names without attempting to establish a connection to the specified server, before I present the list of servers to them. So for example:
Servers: A, B, C, D
Joe has permissions to A and D but not B and C so Joe should only see A and D in his server list.
How should I be tackling this issue? Should I be pulling from Active Directory? I know how to pull my user's identity, but where to pull the server information and find out if a user has permissions is a completely different story. Any documentation, code samples, articles, etc. are helpful.
Notes About The Work Environment
All of the servers in the list are database servers running SQL Server 2008 R2 and higher.
This is a government environment where there are heavy restrictions.
I am unable to modify Active Directory in any way.
I can query Active Directory and SQL Server all day, provided the user has permissions.
Using third party libraries and tools are not authorized.
Side Note About The Server List
This list is stored in a database, however I don't think this really helps with the permissions side. I've already solved the issue of checking permissions against the individual databases with the following SQL query:
SELECT name
FROM sys.databases
WHERE HAS_DBACCESS(name) = 1
ORDER BY name
Thank you for your help!
-Jamie
Conclusion
Essentially, I have found no possible way to query the AD groups on the remote server without attempting to establish a connection, thus the execution of all samples found will flag the users account with IA. For others with a similar issue that don't have to worry about IA dropping a hammer on users, I have included a few solutions below you can try to meet your needs (all of the following will work when implemented correctly).
Solutions
Query the server in active directory and retrieve the groups from the server, if this fails 9 times out of ten the exception message will be 'Access is denied.'. If it succeeds, proceed to pull the current user's groups and compare to the groups pulled from the server. The selected answer to this post combined with this post and/or this post will get you where you need to be with this solution.
If you have access to the server already through impersonation or other means (SQL Server Auth) then you can use the following to see if the member has any server roles assigned:
SELECT IS_SRVROLEMEMBER('public')
You could also use the Login class available in the Microsoft.SqlServer.Smo assembly using the Microsoft.SqlServer.Management.Smo namespace. However, you may or may not have issues moving the Microsoft.SqlServer.SqlClrProvider namespace from the GAC to the BIN. More information about this can be found at this StackOverflow post, and at this Microsoft Connect thread which states the following:
Client applications should not be using the assemblies from the Program Files folders unless they are from the specific SDK folders (such as "C:\Program Files (x86)\Microsoft SQL Server\130\SDK")
You could even do a basic connection test wrapped in a try catch to see if the connection will work.
using (SqlConnection conn = new SqlConnection(connString)) {
try {
conn.Open();
conn.Close();
} catch (Exception e) { Console.Write($"Connection test failed: {e.Message}"); }
}
There are a small variety of ways to achieve the overall goal, it just depends on your particular situation and how you want to approach it. For me, none of the solutions will work since in each scenario the test will attempt a connection to the server in question which will flag the user due to lack of permissions.
If you have Active Directory implemented then you should be giving users access rights to things like servers via AD groups anyways or else that creates a management nightmare. Imagine if John Smith joins your company as a sys admin, are you going to go to every server and explicitly assign him rights? Much easier to just create a server admin AD group then assign it to the server (or dictate what AD groups exists on servers and permission levels by group policy.
Why this also helps you is that when you develop applications, you can use the built in AD role provider to serve up things like this. Here is a simple example of grabbing a users groups by AD user Name
using System.DirectoryServices.AccountManagement;
public List<string> GetGroupNames(string userName)
{
List<string> result = new List<string>();
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAINHERE"))
{
using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(pc, userName).GetGroups(pc))
{
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
}
}
return result;
}
EDIT: So if you absolutely refuse to use active directory groups to manage permissions on servers and buying a tool is out of the question, here is a class that will iterate through all of your local machine groups and give you a list of users within those groups. You could do something like have it run as a scheduled task on the server (or win service) and save it's results back to a DB so you can query or build a UI to pull and monitor this info at any time. This doesn't reach out and grab sql server permissions as you said you already have that.
public class MachinePermissions
{
string machineName { get; set; }
public List<LocalGroup> localGroups { get; set; }
public List<string> GetGroupMembers(string sGroupName)
{
List<String> myItems = new List<String>();
GroupPrincipal oGroupPrincipal = GetGroup(sGroupName);
PrincipalSearchResult<Principal> oPrincipalSearchResult = oGroupPrincipal.GetMembers();
foreach (Principal oResult in oPrincipalSearchResult)
{
myItems.Add(oResult.Name);
}
return myItems;
}
private GroupPrincipal GetGroup(string sGroupName)
{
PrincipalContext oPrincipalContext = GetPrincipalContext();
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, sGroupName);
return oGroupPrincipal;
}
private PrincipalContext GetPrincipalContext()
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine);
return oPrincipalContext;
}
public MachinePermissions()
{
machineName = Environment.MachineName;
PrincipalContext ctx = new PrincipalContext(ContextType.Machine, Environment.MachineName);
GroupPrincipal gp = new GroupPrincipal(ctx);
gp.Name = "*";
PrincipalSearcher ps = new PrincipalSearcher();
ps.QueryFilter = gp;
PrincipalSearchResult<Principal> result = ps.FindAll();
if(result.Count() > 0)
{
localGroups = new List<LocalGroup>();
foreach (Principal p in result)
{
LocalGroup g = new LocalGroup();
g.groupName = p.Name;
g.users = GetGroupMembers(g.groupName);
localGroups.Add(g);
}
}
}
}
public class LocalGroup
{
public string groupName { get; set; }
public List<String> users { get; set; }
}
You can create AD group for accessing each database, then add users to them.
In your app you can add list of groups and check if user Is member of them.
It's common practice and allow to create secure scenarios for different access right for different users. You only set permissions for group once and all members can benefit from access rights.

Validate users of Remote Active Directory in C#

I try to authenticate users belonging to remote ActiveDirectory from my machine, which is not the same domain as the current machine or user domain. There will be no trust between my machine and remote ActiveDirectory machine.
Initial Try
I tried to authenticate a user(Input: sAMAccountName, machine's ipaddress, machine's domain username("Administrator") and machine's password(***). Able to get result that the user with 'sAMAccountName' do exist in ActiveDirectory.
My Requirement:
Imagine that already a user("qwerty") is created in ActiveDirectory
From my local machine, I will have the following information,
a. Remote ActiveDirectory ipaddress
b. Remote ActiveDirectory machine's username and password.
c. Username and password of User "qwerty"
I need to check whether User "qwerty" is present in remote ActiveDirectory's users list and validate whether the password entered is same in ActiveDirectory's Users list
Code I tried:
DirectoryEntry entry = new DirectoryEntry("LDAP://ipaddress/DC=dinesh,DC=com", name, password);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(sAMAccountName=" + name + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
isValid = true;
adsEntry.Close();
}
catch (Exception ex)
{
adsEntry.Close();
}
Do I need to create a trust between local machine and remote ActiveDirectory machine before validating Users in a remote ActiveDirectory? If yes please tell how it can be done;
After creating trust, how can I validate Users?
===========================================================================
I am able to use the solution suggested by Rainer, but with a new problem. When I create a new user via C# code from a different machine, then some properties do not set properly.
Does this need to be set compulsorily while creating user?
First some basics (independent of this question)
Authentication
The system checks if Bob is really Bob. In an Active Directory environment, this is usually done with a domain login from the workstation, Bob enters his username and password, and he gets a Kerberos ticket. Later, if he wants to access e.g. a file share on a remote fileserver, he does not need to login anymore, and can access the files without entering username/password.
Authorization
The system checks which resources Bob is allowed to access. Usually Bob is in domain groups, and a group is in the ACL (access control list) of the resource.
If there are multiple trusting domains, Bob needs to login in one domain, and can access resources in all other domains.
This is one of the main reasons using Active Directory: single sign on
Checking if user / password is valid
If you have a username and password and want to check if the password is valid, you have to do a login to the domain. There is no way of just “checking if the password is correct”.
Login means: if there is a security policy “lock account if more than 3 invalid logins”, the account will be locked out checking with wrong password, even if you “only want to check the user+password”.
Using .NET Directory Service functions
I assume here that the process is either run by a human account as a normal program, or the program is a Windows service or a scheduled task which runs under a domain “technical user” account. In this case, you do not need to provide credentials for using the AD functions. If accessing other trusting AD domains, this is also true.
If you want to login to a “foreign domain”, and there is no trust, you need to provide a username+password (as in your code).
"Manually" authenticating a user
Normally, this should not be needed. Example: ASP.NET intranet usage. The user access a web application on the current domain or trusting domain, the authentication is done “in the background” by browser and IIS (if integrated Windows authentication is on). So you never need to handle user passwords in the application.
I don’t see many use cases where a password is handled by code.
One may that your program is a helper tool for storing emergency user accounts/passwords. And you want to check periodically if these accounts are valid.
This is a simple way to check:
using System.DirectoryServices.AccountManagement;
...
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, "192.168.1.1");
bool userValid = principalContext.ValidateCredentials(name, password);
One can also use the older, raw ADSI functions:
using System.DirectoryServices;
....
bool userOk = false;
string realName = string.Empty;
using (DirectoryEntry directoryEntry =
new DirectoryEntry"LDAP://192.168.1.1/DC=ad,DC=local", name, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
{
searcher.Filter = "(samaccountname=" + name + ")";
searcher.PropertiesToLoad.Add("displayname");
SearchResult adsSearchResult = searcher.FindOne();
if (adsSearchResult != null)
{
if (adsSearchResult.Properties["displayname"].Count == 1)
{
realName = (string)adsSearchResult.Properties["displayname"][0];
}
userOk = true;
}
}
}
If your real requirement is actually a validity check of user+password, you can do it in one of these ways.
However, if it is a "normal application", which just wants to check if the entered credentials are valid, you should rethink your logic. In this case, you better should rely on the single sign on capabilities of AD.
If there are further questions, please comment.
b. Remote ActiveDirectory machine's username and password.
This sounds a bit unclear. I assume you mean "a username and corresponding password in the remote domain".
There is also the concept of a machine account, which is the hostname appended with $. But that's another topic.
Creating new user
Option 1
using (DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://192.168.1.1/CN=Users,DC=ad,DC=local",
name, password))
{
using (DirectoryEntry newUser = directoryEntry.Children.Add("CN=CharlesBarker", "user"))
{
newUser.Properties["sAMAccountName"].Value = "CharlesBarker";
newUser.Properties["givenName"].Value = "Charles";
newUser.Properties["sn"].Value = "Barker";
newUser.Properties["displayName"].Value = "CharlesBarker";
newUser.Properties["userPrincipalName"].Value = "CharlesBarker";
newUser.CommitChanges();
}
}
Option 2
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "192.168.1.1",
"CN=Users,DC=ad,DC=local", name, password))
{
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext))
{
userPrincipal.Name = "CharlesBarker";
userPrincipal.SamAccountName = "CharlesBarker";
userPrincipal.GivenName = "Charles";
userPrincipal.Surname = "Barker";
userPrincipal.DisplayName = "CharlesBarker";
userPrincipal.UserPrincipalName = "CharlesBarker";
userPrincipal.Save();
}
}
I leave as an exercise to you to find out which attribute goes into which User dialog entry field :-)

C# Ask for Domain Admin credential and use them to perform some task

I need some help with examples how to use Credential of a current user running application.
So in windows 7 you can run application using user loged in by simply running application or you can use "Run as a different User" option and run it as another user.
In my Active Directory I have 2 account Domain User and one with Domain Admin rights. I'm login Windows as a Domain User and when I need I'm using "Run as a different User" to launch some task as a Domain Admin.
So the task is to get my Credential and use it to perform some task, lets say rename active directory user name.
Best way to do this as I can see is to ask user running application to enter Domain Admin credential on then start application and use them for various task. Of course I can easily run application with "Run as a different User" but I still need to get this credential and use them.
I've searched through the web and I can't find this, all i could find is using credential for a web auth.
If you can show me some examples how to:
1) Ask user for a Admin user credential ( i can leave without this )
2) Get and use credentials of a user running application
I don't want to know password I know I can't. Don't really want to add to a WPF form password box I prefer to use windows API to handle this i've already entered user name and password using "Run as a different User".
PS: I sorry if this topic exists :( I guess I'm bad at creating correct search requests.
ADDED: to be more clear what I need. In powershell it will look like this:
# This Asks user to enter credentials
$cred = Get-Credential;
# this checks if I have rights to use them.
Get-ADDomain “DOMAIN” –Server “Domain.com” –Credential $cred;
Of course it's simplified as hell though the point is that I can use credentials user entered when ever it's needed.
The equivalent C# to your Get-ADDomain is quite simple, it is just
public void PerformSomeActionAsAdmin(string adminUsername, string adminPassword)
{
//Null causes the constructor to connect to the current domain the machine is on.
// |
// V
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, null, adminUsername, adminPassword))
{
//do something here with ctx, the operations will be performed as whoever's username and password you passed in.
}
}
if you don't want to connect to the current domain and instead want to connect to Domain.com then replace the null with the appropriate string.
EDIT: if you want to use secure strings you can't use System.DirectoryServices.AccountManagement.PrincipalContext, you will need to go with the lower level calls in System.DirectoryServices.Protocols. Doing this process is quite complex, here is a link to the MSDN article "Introduction to System.DirectoryServices.Protocols (S.DS.P)" explaining how to use it. It is a big complex read and honestly I don't think it is worth it to be able to use encrypted strings.
public void PerformSomeActionAsAdmin(NetworkCredential adminCredential)
{
using(LdapConnection connection = new LdapConnection("fabrikam.com", adminCredential))
{
// MAGIC
}
}
Do you want to check if the current user is a doman admin? start by looking at his code, it should help you get started identifying what AD groups the current user is in. This will give you a list of strings that are each group's name the current user belongs to. Then you can check that list against whatever AD group you are trying to check for. Replace YourDomain with your domain name:
WindowsIdentity wi = WindowIdentity.GetCurrent();
List<string> result = new List<string>();
foreach (IdentityReference group in wi.Groups)
{
result.Add(group.Translate(typeof(NTAccount)).ToString().Replace("YourDomain\\", String.Empty));
}
Since i'm not quite sure what you're trying to do, this also might be helpful. You'd have to get the user name and password from a textobx, password box etc. This could be used for an "override" to use, for example, a manager's credentials etc. to do something the current user wasn't allowed to do because of AD group membership etc.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YourDomain"))
{
if (UserName.Contains("YourDomain\\"))
{
UserName = UserName.Replace("YourDomain\\", String.Empty);
}
//validate the credentials
bool IsValid = pc.ValidateCredentials(UserName, Password);
}

Uniquely identify machine in web application using asp.net c#

I have one login page, user can use any machine while login into that page for the first time. once the user logged in for the first time, i need to restrict that user to not login into another machine. So user need to use only one machine that's used for the first time login.
I tried to get the client side mac address, but i can't able to get client side mac address in my website. Is there any other way to identity a machine uniquely?
For asp.net it's not possible to get the mac address of the client. You need to have some kind of windows application for that, that runs on the user's system.
A permanent cookie with a with a GUID might also be a solution.
Another solution might be to look up the servervariables when they make a request you will have Request.ServerVariables["REMOTE_ADDR"]; which would probably be the internal IP if the app is internal/intranet. There is also REMOTE_HOST. Sometimes these are filtered off by proxies/firewalls/nat but hopefully not in your situation.
Hope it helps!
if its intranet webapp, then you can enforce windows authentication - and keep a list of logged in users, in the database, with a timestamp of when the logged in user will automatically logout after the timestamp period.
Alternatively, use a cookie in forms authentication to do just that. But in any case, you will need the list of logged in users, and automatically log the user off, if he is on another machine.
More so, you can get the client's IP address and go from there, but its not reliable as it could be of an ISP. Its tricky, but cookies seems to be the simplest way of doing this.
However, a good solution would be to do it like IRC does, to keep track of logged in users. It sends a PING to the client, and expects the client to return a PONG, at different intervals of time. If the PONG is not received by the client, the IRC server automatically disconnects the user. Try this with something like SignalR. The downside of this is, if the user closes the browser and a PING request comes in, it will bounce back and the client will be disconnected as he/she will not be able to send a PONG request back.
I believe you want a user logged in on the website only in one session at any given time. Problem is that you can't know for sure when the user leaves, if he doesn't logout using the logout button.To fix this you have to have a timeout. I used a text file on the server in an application and it works.
Login button:
protected void btLogin_Click(object sender, EventArgs e)
{
if (check(txtPass.Text) && check(txtUser.Text))
{
var user = new UserManager().login(txtUser.Text, txtPass.Text);
if (user != null)
{
// this is the test you're looking for, the rest is only context
if (!FileManager.alreadyLoggedIn(user.email))
{
FormsAuthentication.SetAuthCookie(user.email, false);
}
else
{
//throw error that it is already connected in some other place
}
}
else
{
//throw error that login details are not OK
}
}
}
In a class two static methods:
//you have to call this function at every request a user makes
internal static void saveUserSessionID(string email)//email or any unique string to user
{
var dir = HostingEnvironment.MapPath("~/temp/UserSession/");// a folder you choose
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
string path = dir + email + ".txt";
File.WriteAllText(path, HttpContext.Current.Session.SessionID);
}
// if a request has not been made in tha last 4 minutes, the user left, closed the browser
// the test checks this only on a real server, localhost is not tested to be easy for the developer
internal static bool alreadyLoggedIn(string email)
{
var file = HostingEnvironment.MapPath("~/temp/UserSession/" + email + ".txt");
return File.Exists(file) && File.GetLastWriteTime(file).AddMinutes(4) > DateTime.Now && !HttpContext.Current.Request.IsLocal;
}
Obviously this is from another application, you can only take the idea and implement it in your own application. You can't just copy paste it.

C#: How to connect to Active Directory with SSL enabled?

The project I am working on will integrate with the customers Active Directory in order to authenticate users. I have been trying to write some code that will retrieve a users password and I understand that Active Directory will only expose the relevant properties over a SSL connection on port 636.
The following code connects programmatically without using SSL but then I can't see the password properties:
static void Main(string[] args)
{
DirectoryEntry entry = new DirectoryEntry(#"LDAP://<IP>/CN=LDAP Test,CN=Users,DC=customer,DC=com");
entry.AuthenticationType = AuthenticationTypes.None;
entry.Username = "CN=LDAP Test,CN=Users,DC=customer,DC=com";
entry.Password = "<password>";
if (entry != null)
{
foreach (Object propName in entry.Properties.PropertyNames)
{
Console.WriteLine((String)propName);
}
}
}
When I change the code to use SSL I get an exception stating ;Unknown error (0x80005000)'.
I have enabled SSL on the server hosting Active Directory, installed a Microsoft CA on the same server and obtained a certificate from the CA.
I can connect to the Active Directory over SSL using Apache Directory Studio but that does not show the password properties.
The following code shows what I have been trying to use to connect using SSL:
static void Main(string[] args)
{
DirectoryEntry entry = new DirectoryEntry(#"LDAPS://<IP>:636/CN=LDAP Test,CN=Users,DC=customer,DC=com");
entry.AuthenticationType = AuthenticationTypes.SecureSocketsLayer;
entry.Username = "CN=LDAP Test,CN=Users,DC=customer,DC=com";
entry.Password = "<password>";
if (entry != null)
{
foreach (Object propName in entry.Properties.PropertyNames)
{
Console.WriteLine((String)propName);
}
}
}
I'm not sure where to go with this and some assistance would be greatly appreciated.
I have been trying to write some code
that will retrieve a users password...
This is unrelated to your SSL problem, but I don't think retrieving a user's password from Active Directory is possible. It only stores a hash and that's why you aren't receiving any kind of "password" property when querying the user's properties.
Updated Answer
After reading your comment, it appears you're looking for the unicodePwd attribute which contains the security hash. According to the MSDN information, writing to that attribute requires the special SSL connection but you still won't be able to read it because it's a write-only attribute.
Specifically from MSDN:
The unicodePwd attribute is never returned by an LDAP search.
Here's also a forum post that I found that seems to say the same thing:
The users' password is stored in the
Active Directory on a user object in
the unicodePwd attribute. This
attribute can be written under
restricted conditions, but it cannot
be read due to security reasons.
(Source)
Try adding the server's certificate and root certificate to your local store. The easiest way to do this is to use IE to connect to https://your.domain.contoller:636. Then click through all the certificate screens and add them to your store.

Categories

Resources