Prerequisite Detail
Working in .NET 2.0.
The code is in a common library that could be called from ASP.Net, Windows Forms, or a Console application.
Running in a Windows Domain on a corporate network.
The Question
What is the best way to get the current user's SID? I'm not talking about the identity that is executing the application, but the user who is accessing the interface. In background applications and desktop based applications this should be the identity actually executing the application, but in ASP.Net (without impersionation) this should be the HttpContext.Current.User SID.
The Current Method
This is what I've got right now. It just seems... wrong. It's nasty. Is there a better way to do this, or some built in class that does it for you?
public static SecurityIdentifier SID
{
get
{
WindowsIdentity identity = null;
if (HttpContext.Current == null)
{
identity = WindowsIdentity.GetCurrent();
}
else
{
identity = HttpContext.Current.User.Identity as WindowsIdentity;
}
return identity.User;
}
}
I don't think there is a better way at getting at this info--you've got to get at the WindowsPrincipal somehow and .NET's rather clean API abstracts that behind the User object. I'd just leave this nice and wrapped up in a method and call it a day.
Well, ok, there is one thing you should do (unless your web users are always going to be WindowsIdentity), which would be to check for null identity and handle it according to whatever rules you have.
Without the use of third-party libraries This code will give correct results, if the user changed his user name.
String SID = "";
string currentUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name.ToString();
RegistryKey regDir = Registry.LocalMachine;
using (RegistryKey regKey = regDir.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData", true))
{
if (regKey != null)
{
string[] valueNames = regKey.GetSubKeyNames();
for (int i = 0; i < valueNames.Length; i++)
{
using (RegistryKey key = regKey.OpenSubKey(valueNames[i], true))
{
string[] names = key.GetValueNames();
for (int e = 0; e < names.Length; e++)
{
if (names[e] == "LoggedOnSAMUser")
{
if (key.GetValue(names[e]).ToString() == currentUserName)
SID = key.GetValue("LoggedOnUserSID").ToString();
}
}
}
}
}
}MessageBox.Show(SID);
The WindowsIdentity class is the "built in class that does it for you". You've got as simple a solution as you're going to get really, as long as you have a valid WindowsIdentity to work with.
Alternatively, if you have the username of the user in question and you want to get the SID directly from AD, you can build your own library to use the DirectoryServices namespace and retrieve the DirectoryEntry for your user (this is a fairly complicated process as DirectoryServices is tricky). You can even use LDAP to get it if you have a need.
//the current user
WindowsIdentity.GetCurrent().Owner
Returns the security identifier (SID) in SDDL format
you know them as S-1-5-9.
WindowsIdentity.GetCurrent().Owner.ToString()
Returns the account domain security identifier (SID)
If the SID does not represent a Windows account SID, this
property returns null
WindowsIdentity.GetCurrent().Owner.AccountDomainSid
You could use this as:
_pipeSecurity = new PipeSecurity();
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, WindowsIdentity.GetCurrent().Owner);
var audid = new PipeAuditRule(sid, PipeAccessRights.FullControl, AuditFlags.Failure | AuditFlags.Success);
var access = new PipeAccessRule(sid, PipeAccessRights.ReadWrite, AccessControlType.Allow);
_pipeSecurity.AddAuditRule(audid);
_pipeSecurity.AddAccessRule(access);
Related
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 need to create a new user in Active Directory. I have found several examples like the following:
using System;
using System.DirectoryServices;
namespace test {
class Program {
static void Main(string[] args) {
try {
string path = "LDAP://OU=x,DC=y,DC=com";
string username = "johndoe";
using (DirectoryEntry ou = new DirectoryEntry(path)) {
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Add(username);
ou.CommitChanges();
}
}
catch (Exception exc) {
Console.WriteLine(exc.Message);
}
}
}
}
When I run this code I get no errors, but no new user is created.
The account I'm running the test with has sufficient privileges to create a user in the target Organizational Unit.
Am I missing something (possibly some required attribute of the user object)?
Any ideas why the code does not give exceptions?
EDIT
The following worked for me:
int NORMAL_ACCOUNT = 0x200;
int PWD_NOTREQD = 0x20;
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Value = username;
user.Properties["userAccountControl"].Value = NORMAL_ACCOUNT | PWD_NOTREQD;
user.CommitChanges();
So there were actually a couple of problems:
CommitChanges must be called on user (thanks Rob)
The password policy was preventing the user to be created (thanks Marc)
I think you are calling CommitChanges on the wrong DirectoryEntry. In the MSDN documentation (http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentries.add.aspx) it states the following (emphasis added by me)
You must call the CommitChanges method on the new entry to make the creation permanent. When you call this method, you can then set mandatory property values on the new entry. The providers each have different requirements for properties that need to be set before a call to the CommitChanges method is made. If those requirements are not met, the provider might throw an exception. Check with your provider to determine which properties must be set before committing changes.
So if you change your code to user.CommitChanges() it should work, if you need to set more properties than just the account name then you should get an exception.
Since you're currently calling CommitChanges() on the OU which hasn't been altered there will be no exceptions.
Assuming your OU path OU=x,DC=y,DC=com really exists - it should work :-)
Things to check:
you're adding a value to the "samAccountName" - why don't you just set its value:
user.Properties["sAMAccountName"].Value = username;
Otherwise you might end up with several samAccountNames - and that won't work.....
you're not setting the userAccountControl property to anything - try using:
user.Properties["userAccountControl"].Value = 512; // normal account
do you have multiple domain controllers in your org? If you, and you're using this "server-less" binding (not specifying any server in the LDAP path), you could be surprised where the user gets created :-) and it'll take several minutes up to half an hour to synchronize across the whole network
do you have a strict password policy in place? Maybe that's the problem. I recall we used to have to create the user with the "doesn't require password" option first, do a first .CommitChanges(), then create a powerful enough password, set it on the user, and remove that user option.
Marc
Check the below code
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
What is the simplest and most efficient way in C# to check if a Windows user account name exists? This is in a domain environment.
Input: user name in [domain]/[user] format (e.g. "mycompany\bob")
Output: True if the user name exists, false if not.
I did find this article but the examples there are related to authenticating and manipulating user accounts, and they assume you already have a user distinguished name, whereas I am starting with the user account name.
I'm sure I can figure this out using AD, but before I do so I was wondering if there is a simple higher level API that does what I need.
* UPDATE *
There are probably many ways to do this, Russ posted one that could work but I couldn't figure out how to tweak it to work in my environment. I did find a different approach, using the WinNT provider that did the job for me:
public static bool UserInDomain(string username, string domain)
{
string path = String.Format("WinNT://{0}/{1},user", domain, username);
try
{
DirectoryEntry.Exists(path);
return true;
}
catch (Exception)
{
// For WinNT provider DirectoryEntry.Exists throws an exception
// instead of returning false so we need to trap it.
return false;
}
}
P.S.
For those who aren't familiar with the API used above: you need to add a reference to System.DirectoryServices to use it.
The link I found that helped me with this: How Can I Get User Information Using ADSI
The examples use ADSI but can be applied to .NET DirectoryServices as well. They also demonstrate other properties of the user object that may be useful.
The System.DirectoryServices namespace in the article is exactly what you need and intended for this purpose. If I recall correctly, it is a wrapper around the Active Directory Server Interfaces COM interfaces
EDIT:
Something like the following should do it (it could probably do with some checking and handling). It will use the domain of the current security context to find a domain controller, but this could easily be amended to pass in a named server.
public bool UserInDomain(string username, string domain)
{
string LDAPString = string.Empty;
string[] domainComponents = domain.Split('.');
StringBuilder builder = new StringBuilder();
for (int i = 0; i < domainComponents.Length; i++)
{
builder.AppendFormat(",dc={0}", domainComponents[i]);
}
if (builder.Length > 0)
LDAPString = builder.ToString(1, builder.Length - 1);
DirectoryEntry entry = new DirectoryEntry("LDAP://" + LDAPString);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "sAMAccountName=" + username;
SearchResult result = searcher.FindOne();
return result != null;
}
and tested with the following
Console.WriteLine(UserInDomain("username","MyDomain.com").ToString());
Found a simple way to do this if you're on a high enough framework version:
using System.DirectoryServices.AccountManagement;
bool UserExists(string userName, string domain) {
using (var pc = new PrincipalContext(ContextType.Domain, domain))
using (var p = Principal.FindByIdentity(pc, IdentityType.SamAccountName, userName)) {
return p != null;
}
}
I would like to a) programatically and b) remotely find out the last date/time that a user successfully logged into a Windows machine (via remote desktop or at the console). I would be willing to take any typical windows language (C, C#, VB, batch files, JScript, etc...) but any solution would be nice.
Try this:
public static DateTime? GetLastLogin(string domainName,string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Domain,domainName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You will need to add references to using using System.DirectoryServices and
System.DirectoryServices.AccountManagement
EDIT: You might be able to get the last login Datetime to a specific machine by doing something like this:
public static DateTime? GetLastLoginToMachine(string machineName, string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Machine, machineName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You can use DirectoryServices to do this in C#:
using System.DirectoryServices;
DirectoryEntry dirs = new DirectoryEntry("WinNT://" + Environment.MachineName);
foreach (DirectoryEntry de in dirs.Children)
{
if (de.SchemaClassName == "User")
{
Console.WriteLine(de.Name);
if (de.Properties["lastlogin"].Value != null)
{
Console.WriteLine(de.Properties["lastlogin"].Value.ToString());
}
if (de.Properties["lastlogoff"].Value != null)
{
Console.WriteLine(de.Properties["lastlogoff"].Value.ToString());
}
}
}
After much research, I managed to put together a nice PowerShell script which takes in a computer name and lists out the accounts and the ACTUAL last logon on the specific computer. ... TURNS OUT MICROSOFT NEVER PROGRAMMED THIS FUNCTION CORRECTLY, IT TRIES TO GET LOGON INFORMATION FROM ACTIVE DIRECTORY AND STORE IT LOCALLY, SO YOU HAVE USERS LOG INTO OTHER MACHINES AND IT'LL UPDATE THE LOGIN DATES ON YOUR MACHINE, BUT THEY NEVER LOGGED INTO YOUR MACHINE.... COMPLETE FAILURE!
$REMOTEMACHINE = "LoCaLhOsT"
$NetLogs = Get-WmiObject Win32_NetworkLoginProfile -ComputerName $REMOTEMACHINE
foreach ($NetLog in $NetLogs)
{
if($NetLog.LastLogon)
{
$LastLogon = [Management.ManagementDateTimeConverter]::ToDateTime($NetLog.LastLogon)
if($LastLogon -ne [DateTime]::MinValue)
{
Write-Host $NetLog.Name ' - ' $LastLogon
}
}
}
Note: From what I can gather, the Win32_NetworkLoginProfile is using the "Win32API|Network Management Structures|USER_INFO_3" structure. So the C# equivalent would be to pInvoke [NetUserEnum] with the level specifying the structures returned in the buffer if you wanted to avoid using WMI.
Fun Fact: The DateTime is returned as an Int32 through USER_INFO_3 structure, so when it's the year 2038, the date will no longer be correct once integer overflow occurs.... #UnixMillenniumBug
You could also use CMD:
C:\Windows\System32>query user /server:<yourHostName>
OR (shorter)
C:\Windows\System32>quser /server:<yourHostName>
Output will be something like this:
USERNAME SESSIONNAME ID STATE LOGONTIME
userXYZ console 1 Active 19.01.2017 08:59
I ended up using pInvoke for "RegQueryInfoKey" in C# to retrieve LastWriteTime on the registry key of each profile located under "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
https://stackoverflow.com/a/45176943/7354452
I would like to display some extra UI elements when the process is being run as Administrator as opposed to when it isn't, similar to how Visual Studio 2008 displays 'Administrator' in its title bar when running as admin. How can I tell?
Technically, if you want to see if the member is the local administrator account, then you can get the security identifier (SID) of the current user through the User property on the WindowsIdentity class, like so (the static GetCurrent method gets the current Windows user):
WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
string sid = windowsIdentity.User.ToString();
The User property returns the SID of the user which has a number of predefined values for various groups and users.
Then you would check to see if the SID has the following pattern, indicating it is the local administrator account (which is a well-known SID):
S-1-5-{other SID parts}-500
Or, if you don't want to parse strings, you can use the SecurityIdentifier class:
// Get the built-in administrator account.
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid,
null);
// Compare to the current user.
bool isBuiltInAdmin = (windowsIdentity.User == sid);
However, I suspect that what you really want to know is if the current user is a member of the administrators group for the local machine. You can get this SID using the WellKnownSidType of BuiltinAdministratorsSid:
// Get the SID of the admin group on the local machine.
var localAdminGroupSid = new SecurityIdentifier(
WellKnownSidType.BuiltinAdministratorsSid, null);
Then you can check the Groups property on the WindowsIdentity of the user to see if that user is a member of the local admin group, like so:
bool isLocalAdmin = windowsIdentity.Groups.
Select(g => (SecurityIdentifier) g.Translate(typeof(SecurityIdentifier))).
Any(s => s == localAdminGroupSid);
I think this is a good simple mechanism.
using System.Security.Principal;
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
bool isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
Here's a one liner to do it.
using System.Security.Principal;
static bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
I felt it important to note the difficulty I had with attempting to use WellKnownSidType.BuiltinAdministratorsSid per casperOne's answer above. According to the WellKnownSiDType MSDN, BuiltinAdministratorsSid "Indicates a SID that matches the administrator account." So I would expect casperOne's code to work, and guess it likely does in some environments. Unfortunately, it didn't on my Windows 2003 with .NET 2.0 (legacy code). It actually returned S-1-5-32-544 which, according to this article is the sid for the Administrators group. Thus, the comparison fails for me. I will have to do my own string comparison for startswith "S-1-5-21" (that kb 243330 indicates the "21" is included even though the blog referenced above does not) and endswith "500".
I hope you solved it, I was looking for someone smart to solve my NamedPipe permission issue, perhaps someone in 2022 likes my answer to your 13-year-old question...
using .net 6.0 > win7 or later ...
Perhaps do something like this and test if what you see makes sense on your account:
var user = WindowsIdentity.GetCurrent();
if (user is not null)
{
logger.LogInformation("{User}", user.Name);
foreach (var item in Enum.GetValues<WellKnownSidType>())
{
try
{
var sid = new SecurityIdentifier(item, user.Owner);
logger.LogInformation($"IsIsWellKnown({item}): {user.Claims.Any(a=> a.Value == sid.Value)}");
}
catch { }
}
}
then if it does you can use something like this:
public static bool Is(WindowsIdentity user, WellKnownSidType type)
{
var sid = new SecurityIdentifier(type, user.Owner);
return user.Claims.Any(a => a.Value == sid.Value);
}
You could be really smart about it and make an extension method by adding the this keyword
public static bool Is(this WindowsIdentity user, WellKnownSidType type)
{
var sid = new SecurityIdentifier(type, user.Owner);
return user.Claims.Any(a => a.Value == sid.Value);
}
You could then use it like so:
WindowsIdentity.GetCurrent().Is(WellKnownSidType.AccountDomainAdminsSid))
I use simple try catch statement to create a random file in "C:\Windows\" folder. If it errors out the app is running with normal privileges otherwise it is running as admin privileges.
try
{
File.Create(string.Format(#"C:\Windows\{0}.txt", new Guid()), 0, FileOptions.DeleteOnClose);
// Do as admin
}
catch
{
// Do as default
}