Calling IPrincipal.IsInRole on Windows 7 - c#

We use NTLM auth in our application to determine whether a user can perform certain operations. We use the IPrincipal of their current Windows login (in WinForms applications), calling IsInRole to check for specific group memberships.
To check that a user is a local administrator on the machine, we use:
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
...
bool allowed = Thread.CurrentPrincipal.IsInRole(#"Builtin\Administrators")
This works if the current user is the Administrator user, or is another user that is a member of the Builtin\Administrators group.
In our testing on Windows 7, we have found that this no longer works as expected. The Administrator user still works fine, but any other user that is a member of the Builtin\Administrators group returns false for the IsInRole call.
What could be causing this difference? I have a gut feeling that a default setting has changed somewhere (possible in gpedit), but cannot find anything that looks like the culprit.

The problem is that Windows security (aka "UAC") is getting in your way. There's special handling of administrator roles and your user will not actually have these roles until he is elevated. Admin roles are "ghosted" in a sense: present but unavailable for permission checks or even to (easily) test for presence. See the note at:
http://msdn.microsoft.com/en-us/library/46ks97y7.aspx
Here's a series that talks about the issue, with example code that does the necessary workarounds:
http://www.simple-talk.com/community/blogs/dana/archive/2008/03/17/45354.aspx
http://www.simple-talk.com/community/blogs/dana/archive/2008/03/17/45352.aspx
I solved a similar problem in an ASP.NET app by building my own UAC prompt and using the name & password to call the Win32 Logon API. You might be lucky enough to be in a .NET desktop app, in which case you can use regular elevation requests.
Here's some C# code to check admin permissions without elevating.
public const UInt32 TOKEN_DUPLICATE = 0x0002;
public const UInt32 TOKEN_IMPERSONATE = 0x0004;
public const UInt32 TOKEN_QUERY = 0x0008;
public enum TOKEN_ELEVATION_TYPE
{
TokenElevationTypeDefault = 1,
TokenElevationTypeFull,
TokenElevationTypeLimited
}
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public static bool IsAdmin()
{
var identity = WindowsIdentity.GetCurrent();
return (null != identity && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator));
}
/// <summary>
/// The function checks whether the primary access token of the process belongs
/// to user account that is a member of the local Administrators group, even if
/// it currently is not elevated.
/// </summary>
/// <returns>
/// Returns true if the primary access token of the process belongs to user
/// account that is a member of the local Administrators group. Returns false
/// if the token does not.
/// </returns>
public static bool CanBeAdmin()
{
bool fInAdminGroup = false;
IntPtr hToken = IntPtr.Zero;
IntPtr hTokenToCheck = IntPtr.Zero;
IntPtr pElevationType = IntPtr.Zero;
IntPtr pLinkedToken = IntPtr.Zero;
int cbSize = 0;
if (IsAdmin())
return true;
try
{
// Check the token for this user
hToken = WindowsIdentity.GetCurrent().Token;
// Determine whether system is running Windows Vista or later operating
// systems (major version >= 6) because they support linked tokens, but
// previous versions (major version < 6) do not.
if (Environment.OSVersion.Version.Major >= 6)
{
// Running Windows Vista or later (major version >= 6).
// Determine token type: limited, elevated, or default.
// Allocate a buffer for the elevation type information.
cbSize = sizeof(TOKEN_ELEVATION_TYPE);
pElevationType = Marshal.AllocHGlobal(cbSize);
if (pElevationType == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Retrieve token elevation type information.
if (!GetTokenInformation(hToken,
TOKEN_INFORMATION_CLASS.TokenElevationType, pElevationType, cbSize, out cbSize))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Marshal the TOKEN_ELEVATION_TYPE enum from native to .NET.
TOKEN_ELEVATION_TYPE elevType = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(pElevationType);
// If limited, get the linked elevated token for further check.
if (elevType == TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
{
// Allocate a buffer for the linked token.
cbSize = IntPtr.Size;
pLinkedToken = Marshal.AllocHGlobal(cbSize);
if (pLinkedToken == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Get the linked token.
if (!GetTokenInformation(hToken,
TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken,
cbSize, out cbSize))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Marshal the linked token value from native to .NET.
hTokenToCheck = Marshal.ReadIntPtr(pLinkedToken);
}
}
// CheckTokenMembership requires an impersonation token. If we just got
// a linked token, it already is an impersonation token. If we did not
// get a linked token, duplicate the original into an impersonation
// token for CheckTokenMembership.
if (hTokenToCheck == IntPtr.Zero)
{
if (!DuplicateToken(hToken, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, ref hTokenToCheck))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
// Check if the token to be checked contains admin SID.
WindowsIdentity id = new WindowsIdentity(hTokenToCheck);
WindowsPrincipal principal = new WindowsPrincipal(id);
fInAdminGroup = principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch
{
return false;
}
finally
{
// Centralized cleanup for all allocated resources.
if (pElevationType != IntPtr.Zero)
{
Marshal.FreeHGlobal(pElevationType);
pElevationType = IntPtr.Zero;
}
if (pLinkedToken != IntPtr.Zero)
{
Marshal.FreeHGlobal(pLinkedToken);
pLinkedToken = IntPtr.Zero;
}
}
return fInAdminGroup;
}
It's adapted from an article I found online somewhere, sorry, lost the attribution.

This worked for me - all I needed was to check if the program had been started in an admin role:
public static bool IsAdminRole()
{
AppDomain domain = Thread.GetDomain();
domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal principle = (WindowsPrincipal)Thread.CurrentPrincipal;
return principle.IsInRole(WindowsBuiltInRole.Administrator);
}
Hope someone finds that of use!
Mike

I found another article here on stackoverflow which tackles this another way.
I adapted it into a method below.
Using Windows 7, this returned true for admins, false for non-admins, and true for non-admin when 'Run as administrator'.
It looks like this will only work with .Net 3.5 and XP SP2 and later, based on an initial glance at MSDN for the PrincipleContext class.
private static bool IsUserAdmin()
{
bool isAdmin = false;
WindowsIdentity wi = WindowsIdentity.GetCurrent();
WindowsPrincipal wp = new WindowsPrincipal(wi);
isAdmin = wp.IsInRole(WindowsBuiltInRole.Administrator);
Console.WriteLine(isAdmin); // False for Windows 7 even if user is admin
//found the code below at [http://stackoverflow.com/questions/1089046/in-net-c-test-if-user-is-an-administrative-user][1]
// Add reference to System.DirectoryServices.AccountManagement (Add Referemce -> .Net)
// Add using System.DirectoryServices.AccountManagement;
if (!isAdmin) //PrincipleContext takes a couple seconds, so I don't use it if not necessary
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Machine, null))
{
UserPrincipal up = UserPrincipal.Current;
GroupPrincipal gp = GroupPrincipal.FindByIdentity(pc, "Administrators");
if (up.IsMemberOf(gp))
{
isAdmin = true;
}
}
}
Console.WriteLine(isAdmin); // True for Windows 7 if user is admin
return isAdmin;
}

Your application is not elevated. Under normal circumstances UAC strips away the "administrator-ness" of the user. If the app can only be used by admins, add a manifest that causes it to elevate so they can keep their admin-ness. If it can be used by either, your best bet is to partition to two parts, one with an elevating manifest and one without, and launch the elevated part from a button or menu item that is decorated with the shield so users won't click it if they're not admins. (On older OS's the message to put the shield on the button will be ignored.) Searching on "UAC", "partition" and "shellexecute" will be helpful.

I've used the same approach as DavB.cs: http://tieledeclercq.blogspot.be/2013/09/c-is-this-valid-administrator-that-can.html
With a few differences:
The administrator could be a nested member of the local administrator group.
I needed to use external credentials (not as the current user).

Related

how to check if current user is in admin group c#

I have read the relevant Stack Overflow questions and tried out the following code:
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (null != identity)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
return false;
It does not return true even though I have manually confirmed that the current user is a member of the local built-in Administrators group.
What am I missing ?
Thanks.
Just found other way to check if user is admin, not running application as admin:
private static bool IsAdmin()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if (identity != null)
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
List<Claim> list = new List<Claim>(principal.UserClaims);
Claim c = list.Find(p => p.Value.Contains("S-1-5-32-544"));
if (c != null)
return true;
}
return false;
}
Credit to this answer, but code is corrected a bit.
The code you have above seemed to only work if running as an administrator, however you can query to see if the user belongs to the local administrators group (without running as an administrator) by doing something like the code below. Note, however, that the group name is hard-coded, so I guess you would have some localization work to do if you want to run it on operating systems of different languages.
using (var pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
using (var up = UserPrincipal.FindByIdentity(pc, WindowsIdentity.GetCurrent().Name))
{
return up.GetAuthorizationGroups().Any(group => group.Name == "Administrators");
}
}
Note that you can also get a list of ALL the groups the user is a member of by doing this inside the second using block:
var allGroups = up.GetAuthorizationGroups();
But this will be much slower depending on how many groups they're a member of. For example, I'm in 638 groups and it takes 15 seconds when I run it.

Creating user on local machine is really slow when adding to groups

I have the following code that takes a username and a password and then creates the user on the machine and adds them to two specific groups. When I get to the group part it is really slow and I have no idea why. My last run according to my logs file says that adding the user to the Users group took 7 minutes, but the IIS_IUSRS was super fast.
below is my initial code that calls the methods that do the real work. I have tried using a task to help speed up the process of checking for groups, but it still runs super slow.
public void Apply(Section.User.User user, Action<string> status)
{
#region Sanity Checks
if (user == null)
{
throw new ArgumentNullException("user");
}
if (status == null)
{
throw new ArgumentNullException("status");
}
#endregion
_logger.Debug(string.Format("Starting to apply the user with name {0}", user.UserName));
status(string.Format("Applying User {0} to the system.", user.UserName));
using (PrincipalContext pc = new PrincipalContext(ContextType.Machine))
{
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(pc, user.UserName);
try
{
_logger.Debug("Checking if user already exists");
if (userPrincipal == null)
{
userPrincipal = CreateNewUser(user, pc);
}
_logger.Debug("Setting user password and applying to the system.");
userPrincipal.SetPassword(user.UserPassword);
userPrincipal.Save();
Task<PrincipalSearchResult<Principal>> groups =
Task<PrincipalSearchResult<Principal>>.Factory.StartNew(userPrincipal.GetGroups);
_logger.Debug("Adding user to the groups.");
AddUserToGroups(pc, userPrincipal, groups, user.UserType.Equals(UserType.WorkerProcess.ToString()) ? "Administrators" : "Users", "IIS_IUSRS");
AddCurrentUser(user);
}
finally
{
if (userPrincipal != null)
{
userPrincipal.Dispose();
}
}
}
}
This is my private method I use to create the user if it doesn't exist.
private UserPrincipal CreateNewUser(Section.User.User user, PrincipalContext principal)
{
_logger.Debug("User did not exist creating now.");
UserPrincipal newUser = new UserPrincipal(principal)
{
Name = user.UserName,
Description = user.UserDescription,
UserCannotChangePassword = false,
PasswordNeverExpires = true,
PasswordNotRequired = false
};
_logger.Debug("User created.");
return newUser;
}
Below is the logic for the groups. I have made a comment above the offending code that I get hung on whenever I walk through with the debugger. Also the debug log entry is always the last one I get before the hang as well.
private void AddUserToGroups(PrincipalContext principal, UserPrincipal user, Task<PrincipalSearchResult<Principal>> userGroups, params string[] groups)
{
groups.AsParallel().ForAll(s =>
{
using (GroupPrincipal gp = GroupPrincipal.FindByIdentity(principal, s))
{
_logger.Debug(string.Format("Checking if user is alread in the group."));
if (gp != null && !userGroups.Result.Contains(gp))
{
_logger.Debug(string.Format("The user was not a member of {0} adding them now.", gp.Name));
//This is the point that the 7 minute hang starts
gp.Members.Add(user);
gp.Save();
_logger.Debug(string.Format("User added to {0}.", gp.Name));
}
}
});
}
Any help with this would be greatly appreciated as this project is expected to release in October, but I can't release with a 7 minute hang when creating a user.
Had the same problem. It seems that
gp.Members.Add( user );
is slow because it first enumerates groups (to get Members) and only then it adds to the collection (which adds another slowdown).
The solution was to have it like:
UserPrincipal user = this is your user;
GroupPrincipal group = this is your group;
// this is fast
using ( DirectoryEntry groupEntry = group.GetUnderlyingObject() as DirectoryEntry )
using ( DirectoryEntry userEntry = user.GetUnderlyingObject() as DirectoryEntry )
{
groupEntry.Invoke( "Add", new object[] { userEntry.Path } );
}
//group.Members.Add(user); // and this is slow!
//group.Save();
Just a tip - creating passwords with SetPassword was also terribly slow for us. The solution was to follow the approach from "The .NET Developer's Guide to Directory Services Programming" where they use a low level password setting using LdapConnection from System.DirectoryServices.Protocols.
The last bottleneck we've discovered was caused by the User.GetGroups() method.
Anyway, drop a note if the code for adding users to groups makes a difference for you. Also note that you don't really need to perform this in parallel - I understand that this was your approach to speed up the code but you don't really need this.

How to prevent an app from being pinned in Windows 7?

I am trying to prevent the user from pinning my .NET app to the taskbar. I've found some code on the Old New Thing that does just that. However, it is in C++.
#include <shellapi.h>
#include <propsys.h>
#include <propkey.h>
HRESULT MarkWindowAsUnpinnable(HWND hwnd)
{
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr)) {
PROPVARIANT var;
var.vt = VT_BOOL;
var.boolVal = VARIANT_TRUE;
hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
pps->Release();
}
return hr;
}
BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
MarkWindowAsUnpinnable(hwnd);
return TRUE;
}
I am having very little luck converting it to c#. Can someone help?
You can download the Windows API Code Pack which has the necessary p/invoke calls you need to translate the code in your post to C#.
Either use the library in whole or find the specific calls and definitions you require (search it for SHGetPropertyStoreForWindow and then its other dependencies).
In the question, the Old New Thing post also talks about how you can set some registry settings on per application basis that will so prevent the pinning an application to the taskbar.
All you have to do is add the value of "NoStartPage" to a key for your application under the Root\Applications. The value can be blank and of any type, if Windows just sees it is there it will not show the ability to pin the app, when the user right clicks on it in the taskbar.
Here is the documentation from Microsoft on this feature: Use Registry to prevent pinning of an application
The one caveat to this is that in Windows 7, due to UAC, you have to run as administrator to update the registry. I did this via the app.manifest.
The code to find the right and update the correct registry keys is below (hopefully it is not too verbose):
public static void Main(string[] args)
{
// Get Root
var root = Registry.ClassesRoot;
// Get the Applications key
var applicationsSubKey = root.OpenSubKey("Applications", true);
if (applicationsSubKey != null)
{
bool updateNoStartPageKey = false;
// Check to see if your application already has a key created in the Applications key
var appNameSubKey = applicationsSubKey.OpenSubKey("MyAppName.exe", true);
if (appNameSubKey != null)
{
// Check to see if the NoStartPage value has already been created
if (!appNameSubKey.GetValueNames().Contains("NoStartPage"))
{
updateNoStartPageKey = true;
}
}
else
{
// create key for your application in the Applications key under Root
appNameSubKey = applicationsSubKey.CreateSubKey("MyAppName.exe", RegistryKeyPermissionCheck.Default);
if (appNameSubKey != null)
{
updateNoStartPageKey = true;
}
}
if (updateNoStartPageKey)
{
// Create/update the value for NoStartPage so Windows will prevent the app from being pinned.
appNameSubKey.SetValue("NoStartPage", string.Empty, RegistryValueKind.String);
}
}
}
Using the WindowsAPICodePack (via NuGet) you need code resembling:
// Ensure the handle is available
new WindowInteropHelper(window).EnsureHandle();
// Prevent the window from being pinned to the task bars
var preventPinningProperty = new PropertyKey(
new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 9);
WindowProperties.SetWindowProperty(window, preventPinningProperty, "1");

Strange Error When Using System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials with SAM

I am hosting a WCF Web Service with IIS 6.0. My application pool is running under a local Administrator account, and I have other local users defined for accessing the Web Service. I've written the following code to validate users:
//Any public static (Shared in Visual Basic) members of this type are thread safe
public static PrincipalContext vpc;
//static initializer
static UserManagement()
{
vpc = new PrincipalContext(ContextType.Machine);
}
//Determines whether the given credentials are for a valid Administrator
public static bool validateAdminCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
if (vpc.ValidateCredentials(username, password))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
{
foreach (GroupPrincipal gp in user.GetGroups())
{
try
{
if (gp.Name.Equals("Administrators"))
{
return true;
}
}
finally
{
gp.Dispose();
}
}
}
}
return false;
} //end using PrincipalContext
}
...and in another class:
//verify the user's password
if (!UserManagement.vpc.ValidateCredentials(username, password))
{
string errorMsg = string.Format("Invalid credentials received for user '{0}'", username);
throw new Exception(errorMsg);
}
(Note: I am using the public static PrincipalContext (vpc) solely for calling the ValidateCredentials method; I create a different, temporary PrincipalContext for creating, deleting, and finding users and groups, as I got various COM-related errors when I tried using the global PrincipalContext for everything).
So, most of the time, this code works wonderfully. However, intermittently, I get the following error:
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)
Application:
System.DirectoryServices.AccountManagement
Stack Trace:
at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password)
at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
at MyNamespace.User..ctor(String username, String password)
Once the error occurs, I continue to get it until I restart my entire server (not just IIS). I've tried restarting my application pool and/or IIS, but the error does not go away until I restart the machine. I've also tried instantiating (via a using block) a new PrincipalContext for every call to ValidateCredentials (which I shouldn't have to do), but I still eventually get the same error. From what I've read on System.DirectoryServices.AccountManagement (msdn docs, articles), I believe I'm using it correctly, but this error is crippling my application! I need (and should be able) to validate local user credentials from web service requests coming from multiple clients. Am I doing something wrong? Any help on solving this issue would be much appreciated...
"Any public static (Shared in Visual Basic) members of this type are thread safe"
This boilerplate text confuses a lot of people. It means that any static members exposed by the type are thread-safe. It doesn't mean that any instance of the type stored in a static member will be thread-safe.
Your validateAdminCredentials method creates a new PrincipalContext object, but then proceeds to use the static vpc instance to validate the credentials. Since you have no locks around the access to the static instance, and the instance methods are not thread-safe, you'll eventually get two threads trying to access the same instance at the same time, which will not work.
Try removing the vpc field and using the principalContext instance to validate the credentials. You'll need to create and dispose of the PrincipalContext on every call.
Also, rather than manually iterating the user's groups, you can use the IsMemberOf method to test for membership in a group.
public static bool ValidateCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
return principalContext.ValidateCredentials(username, password);
}
}
public static bool validateAdminCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
if (principalContext.ValidateCredentials(username, password))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
using (GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, "Administrators"))
{
if (null != group && user.IsMemberOf(group))
{
return true;
}
}
}
return false;
}
}
My suggestion would be to not use ValidateCredentials with the SAM provider. I believe it should work, but I am also not surprised that it runs into problems. I think you'll do better by doing a p/invoke into LogonUser using Network logon type for programmatic credential validation.
I'd like to see if Microsoft has a known issue and solution to this problem but in the meantime I'd suggest just coding around it.
If you need a solution the works even with Windows 2000 and w/o running code as system you could also use this:
public static bool ValidateUser(
string userName,
string domain,
string password)
{
var tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
var isLoggedOn = false;
tcpListener.BeginAcceptTcpClient(
delegate(IAsyncResult asyncResult)
{
using (var serverSide =
new NegotiateStream(
tcpListener.EndAcceptTcpClient(
asyncResult).GetStream()))
{
try
{
serverSide.AuthenticateAsServer(
CredentialCache.DefaultNetworkCredentials,
ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
var id = (WindowsIdentity)serverSide.RemoteIdentity;
isLoggedOn = id != null;
}
catch (InvalidCredentialException) { }
}
}, null);
var ipEndpoint = (IPEndPoint) tcpListener.LocalEndpoint;
using (var clientSide =
new NegotiateStream(
new TcpClient(
ipEndpoint.Address.ToString(),
ipEndpoint.Port).GetStream()))
{
try
{
clientSide.AuthenticateAsClient(
new NetworkCredential(
userName,
password,
domain),
"",
ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
}
catch(InvalidCredentialException){}
}
tcpListener.Stop();
return isLoggedOn;
}
I resolved this same problem by using the domain context. It is possible to use the machine context to delegate to the domain, but it appears that you will be forced to run into this problem eventually.
Note that you should create the domain context fresh for each call, because it must be created using the domain of the user you want to authenticate.
My application needed to support machine users as well. I found that whenever a user was a machine user, the domain context would throw an exception. If I catch the exception, I work against the machine context.

Determining members of local groups via C#

I wondered whether anybody knows how to obtain membership of local groups on a remote server programmatically via C#. Would this require administrator permissions? And if so is there any way to confirm the currently logged in user's membership (or not) of these groups?
Howto: (Almost) Everything In Active Directory via C# is very helpfull and also includes instructions on how to iterate AD members in a group.
public ArrayList Groups(string userDn, bool recursive)
{
ArrayList groupMemberships = new ArrayList();
return AttributeValuesMultiString("memberOf", userDn,
groupMemberships, recursive);
}
You will also need this function:
public ArrayList AttributeValuesMultiString(string attributeName,
string objectDn, ArrayList valuesCollection, bool recursive)
{
DirectoryEntry ent = new DirectoryEntry(objectDn);
PropertyValueCollection ValueCollection = ent.Properties[attributeName];
IEnumerator en = ValueCollection.GetEnumerator();
while (en.MoveNext())
{
if (en.Current != null)
{
if (!valuesCollection.Contains(en.Current.ToString()))
{
valuesCollection.Add(en.Current.ToString());
if (recursive)
{
AttributeValuesMultiString(attributeName, "LDAP://" +
en.Current.ToString(), valuesCollection, true);
}
}
}
}
ent.Close();
ent.Dispose();
return valuesCollection;
}
If you do now want to use this AD-method, you could use the info in this article, but it uses unmanaged code:
http://www.codeproject.com/KB/cs/groupandmembers.aspx
The sample application that they made:
It appears there is a new Assembly in .net 3.5 called System.DirectoryServices.AccountManagement which gives a cleaner implementation than System.DirectoryServices. Dominick Baier blogs about a couple of simple operations including checking membership of a group:-
public static bool IsUserInGroup(string username, string groupname, ContextType type)
{
PrincipalContext context = new PrincipalContext(type);
UserPrincipal user = UserPrincipal.FindByIdentity(
context,
IdentityType.SamAccountName,
username);
GroupPrincipal group = GroupPrincipal.FindByIdentity(
context, groupname);
return user.IsMemberOf(group);
}
I think I will use this approach, thanks for the suggestions though however! :-)
Perhaps this is something that can be done via WMI?
I asked a similar question, and ended up writing an answer which used WMI to enum the group members. I had real problems with authentication in the system.directoryservices.accountmanagement stuff. YMMV, of course.
I'd be curious if the System.DirectoryServices.AccountManagement is fully managed. I've used System.DirectoryServices.ActiveDirectory which is a wrapper for COM Interop which has led to many headaches...
This may possibly help. I had to develop an app where we want to authenticate against active directory, and also examine the groups strings that the user is in.
For a couple of reasons we don't want to use windows authentication, but rather have our own forms based authentication. I developed the routine below to firstly authenticate the user, and secondly examine all the groups that the user belongs to. Perhaps it may help. The routine uses LogonUser to authenticate, and then gets the list of numerical guid-like group ids (SIDs) for that user, and translates each one to a human readable form.
Hope this helps, I had to synthesise this approach from a variety of different google searches.
private int validateUserActiveDirectory()
{
IntPtr token = IntPtr.Zero;
int DBgroupLevel = 0;
// make sure you're yourself -- recommended at msdn http://support.microsoft.com/kb/248187
RevertToSelf();
if (LogonUser(txtUserName.Value, propDomain, txtUserPass.Text, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token) != 0) {
// ImpersonateLoggedOnUser not required for us -- we are not doing impersonated stuff, but leave it here for completeness.
//ImpersonateLoggedOnUser(token);
// do impersonated stuff
// end impersonated stuff
// ensure that we are the original user
CloseHandle(token);
RevertToSelf();
System.Security.Principal.IdentityReferenceCollection groups = Context.Request.LogonUserIdentity.Groups;
IdentityReference translatedGroup = default(IdentityReference);
foreach (IdentityReference g in groups) {
translatedGroup = g.Translate(typeof(NTAccount));
if (translatedGroup.Value.ToLower().Contains("desired group")) {
inDBGroup = true;
return 1;
}
}
}
else {
return 0;
}
}

Categories