Вызов IPrincipal.IsInRole в Windows 7
Мы используем аутентификацию NTLM в нашем приложении, чтобы определить, может ли пользователь выполнять определенные операции. Мы используем IPrincipal их текущего входа в Windows (в приложениях WinForms), вызывая IsInRole для проверки членства в определенных группах.
Чтобы проверить, что пользователь является локальным администратором на машине, мы используем:
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
...
bool allowed = Thread.CurrentPrincipal.IsInRole(@"Builtin\Administrators")
Это работает, если текущий пользователь является Administrator
пользователь, или другой пользователь, который является членом Builtin\Administrators
группа.
В нашем тестировании на Windows 7 мы обнаружили, что это больше не работает, как ожидалось. Administrator
Пользователь по-прежнему работает нормально, но любой другой пользователь, который является членом Builtin\Administrators
группа возвращает ложь для IsInRole
вызов.
Что может быть причиной этой разницы? У меня есть ощущение, что настройки по умолчанию где-то изменились (возможно, в gpedit), но я не могу найти ничего похожего на виновника.
5 ответов
Проблема в том, что безопасность Windows (также известная как "UAC") мешает вам. Существует специальная обработка ролей администратора, и у вашего пользователя фактически не будет этих ролей, пока он не будет повышен. Роли администратора являются "призрачными" в некотором смысле: присутствуют, но недоступны для проверки разрешений или даже (легко) проверяют наличие. См. Примечание по адресу: http://msdn.microsoft.com/en-us/library/46ks97y7.aspx
Вот серия, в которой говорится о проблеме, с примером кода, который делает необходимые обходные пути:
- 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
Я решил аналогичную проблему в приложении ASP.NET, создав собственное приглашение UAC и используя имя и пароль для вызова Win32 Logon API. Возможно, вам посчастливится оказаться в настольном приложении.NET, и в этом случае вы можете использовать обычные запросы на повышение прав.
Вот код C# для проверки прав администратора без повышения прав.
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;
}
Это адаптировано из статьи, которую я где-то нашел в Интернете, извините, потерял атрибуцию.
Это сработало для меня - все, что мне нужно было проверить, была ли программа запущена в роли администратора:
public static bool IsAdminRole()
{
AppDomain domain = Thread.GetDomain();
domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal principle = (WindowsPrincipal)Thread.CurrentPrincipal;
return principle.IsInRole(WindowsBuiltInRole.Administrator);
}
Надеюсь, кто-то найдет это полезным!
Майк
Я нашел еще одну статью здесь на stackru, которая занимается этим по-другому. Я адаптировал его в метод ниже. При использовании Windows 7 это возвращало true для администраторов, false для не-администраторов и true для не-администраторов при "Запуск от имени администратора". Похоже, что это будет работать только с.Net 3.5 и XP SP2 и более поздними версиями, основываясь на первоначальном взгляде на MSDN для класса PrincipleContext.
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://stackru.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;
}
Ваша заявка не повышена. В обычных условиях UAC удаляет "администраторские права" пользователя. Если приложение может использоваться только администраторами, добавьте манифест, который заставляет его подняться, чтобы они могли сохранять свои права администратора. Если он может использоваться любым из них, лучше всего разделить его на две части, одну с возвышенным манифестом, а другую без, и запустить приподнятую часть из кнопки или пункта меню, который украшен щитом, чтобы пользователи не нажимали это если они не администраторы. (В более старых ОС сообщение о надевании экрана на кнопку будет игнорироваться.) Поиск по "UAC", "partition" и "shellexecute" будет полезен.
Я использовал тот же подход, что и DavB.cs: http://tieledeclercq.blogspot.be/2013/09/c-is-this-valid-administrator-that-can.html
С некоторыми отличиями:
- Администратор может быть вложенным членом группы локальных администраторов.
- Мне нужно было использовать внешние учетные данные (не как текущий пользователь).