Nota: Es una pregunta que me encuentro de forma recurrente en los foros de desarrollo, así que lo apunto aquí para tener una referencia.
El escenario
Cuando desarrollamos una aplicación de escritorio, puede ser interesante saber a qué grupos pertenece el usuario que está ejecutando nuestra aplicación, para mostrar / ocultar / permitir / revocar ciertas acciones, u opciones. Por ejemplo, yo acostumbro a tener un botón en la barra de estado de mis aplicaciones que permite cambiar la cadena de conexión, y evidentemente, solo está visible cuando el usuario pertenece al grupo “Administradores del dominio”.
IsInRole
Para ello, el objeto WindowsPrincipal dispone de un método IsInRole, que nos dirà si un usuario pertenece a un grupo determinado. Genial, además este método tiene varias sobrecargas, de modo que podemos usarlo pasando el RID, SID, el nombre e incluso una constante basada en la enumeración WindowsBuiltIOnRole:
| Nombre de miembro |
Descripción |
| AccountOperator |
Los operadores de cuentas administran las cuentas de los usuarios de un equipo o dominio. |
| Administrator |
Los administradores tienen acceso completo y sin restricciones al equipo o dominio. |
| BackupOperator |
Los operadores de copia de seguridad pueden reemplazar las restricciones de seguridad con el único propósito de hacer copias de seguridad de los archivos o de restaurarlas. |
| Guest |
Los invitados tienen más restricciones que los usuarios. |
| PowerUser |
Los usuarios avanzados poseen la mayoría de los permisos administrativos, con algunas restricciones. De este modo, los usuarios avanzados pueden ejecutar aplicaciones heredadas, además de aplicaciones certificadas. |
| PrintOperator |
Los operadores de impresión pueden tomar el control de una impresora. |
| Replicator |
Los replicadores permiten la duplicación de archivos en un dominio. |
| SystemOperator |
Los operadores del sistema administran un equipo en particular. |
| User |
Los usuarios no pueden realizar cambios accidentales o intencionados en todo el sistema. En consecuencia, pueden ejecutar aplicaciones certificadas, pero no la mayoría de las aplicaciones heredadas. |
De modo que para saber si nuestro usuario es administrador local, basta con hacer esto:
WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return wp.IsInRole(WindowsBuiltInRole.Administrator);
Sencillo, verdad? A partir del usuario que ejecuta nuestra aplicación (válido también en caso de impersonación), creamos un objeto Principal e invocamos al método pasándole el grupo contra el que deseamos validar.
El problema de esta enumeración es que como su nombre indica, sólo contempla los grupos locales. Así que si queremos saber si pertenece a un grupo del dominio parece que tendremos que buscar el SID del grupo, o el nombre, y hardcodearlo ‘a mano’ en nuestra aplicación.
Feo verdad? Pues la verdad es que si, muy feo… vamos a investigar un poco más, a ver si encontramos otra forma.
Nota: Por motivos de rendimiento, para determinar la función del usuario se recomienda utilizar la sobrecarga de IsInRole(SecurityIdentifier) como sobrecarga preferible.
WellKnownSidType
Existe una enumeración llamada WellKnownSidType, que devuelve los identificadores de seguridad más utilizados, vamos a darle un vistazo:
|
Member name
|
Description |
| NullSid |
Indicates a null SID. |
| WorldSid |
Indicates a SID that matches everyone. |
| LocalSid |
Indicates a local SID. |
| CreatorOwnerSid |
Indicates a SID that matches the owner or creator of an object. |
| CreatorGroupSid |
Indicates a SID that matches the creator group of an object. |
| CreatorOwnerServerSid |
Indicates a creator owner server SID. |
| CreatorGroupServerSid |
Indicates a creator group server SID. |
| NTAuthoritySid |
Indicates a SID for the Windows NT authority. |
| DialupSid |
Indicates a SID for a dial-up account. |
| NetworkSid |
Indicates a SID for a network account. This SID is added to the process of a token when it logs on across a network. |
| BatchSid |
Indicates a SID for a batch process. This SID is added to the process of a token when it logs on as a batch job. |
| InteractiveSid |
Indicates a SID for an interactive account. This SID is added to the process of a token when it logs on interactively. |
| ServiceSid |
Indicates a SID for a service. This SID is added to the process of a token when it logs on as a service. |
| AnonymousSid |
Indicates a SID for the anonymous account. |
| ProxySid |
Indicates a proxy SID. |
| EnterpriseControllersSid |
Indicates a SID for an enterprise controller. |
| SelfSid |
Indicates a SID for self. |
| AuthenticatedUserSid |
Indicates a SID for an authenticated user. |
| RestrictedCodeSid |
Indicates a SID for restricted code. |
| TerminalServerSid |
Indicates a SID that matches a terminal server account. |
| RemoteLogonIdSid |
Indicates a SID that matches remote logons. |
| LogonIdsSid |
Indicates a SID that matches logon IDs. |
| LocalSystemSid |
Indicates a SID that matches the local system. |
| LocalServiceSid |
Indicates a SID that matches a local service. |
| NetworkServiceSid |
Indicates a SID that matches a network service. |
| BuiltinDomainSid |
Indicates a SID that matches the domain account. |
| BuiltinAdministratorsSid |
Indicates a SID that matches the administrator account. |
| BuiltinUsersSid |
Indicates a SID that matches built-in user accounts. |
| BuiltinGuestsSid |
Indicates a SID that matches the guest account. |
| BuiltinPowerUsersSid |
Indicates a SID that matches the power users group. |
| BuiltinAccountOperatorsSid |
Indicates a SID that matches the account operators account. |
| BuiltinSystemOperatorsSid |
Indicates a SID that matches the system operators group. |
| BuiltinPrintOperatorsSid |
Indicates a SID that matches the print operators group. |
| BuiltinBackupOperatorsSid |
Indicates a SID that matches the backup operators group. |
| BuiltinReplicatorSid |
Indicates a SID that matches the replicator account. |
| BuiltinPreWindows2000CompatibleAccessSid |
Indicates a SID that matches pre-Windows 2000 compatible accounts. |
| BuiltinRemoteDesktopUsersSid |
Indicates a SID that matches remote desktop users. |
| BuiltinNetworkConfigurationOperatorsSid |
Indicates a SID that matches the network operators group. |
| AccountAdministratorSid |
Indicates a SID that matches the account administrators group. |
| AccountGuestSid |
Indicates a SID that matches the account guest group. |
| AccountKrbtgtSid |
Indicates a SID that matches the account Kerberos target group. |
| AccountDomainAdminsSid |
Indicates a SID that matches the account domain administrator group. |
| AccountDomainUsersSid |
Indicates a SID that matches the account domain users group. |
| AccountDomainGuestsSid |
Indicates a SID that matches the account domain guests group. |
| AccountComputersSid |
Indicates a SID that matches the account computer group. |
| AccountControllersSid |
Indicates a SID that matches the account controller group. |
| AccountCertAdminsSid |
Indicates a SID that matches the certificate administrators group. |
| AccountSchemaAdminsSid |
Indicates a SID that matches the schema administrators group. |
| AccountEnterpriseAdminsSid |
Indicates a SID that matches the enterprise administrators group. |
| AccountPolicyAdminsSid |
Indicates a SID that matches the policy administrators group. |
| AccountRasAndIasServersSid |
Indicates a SID that matches the RAS and IAS server account. |
| NtlmAuthenticationSid |
Indicates a SID present when the Microsoft NTLM authentication package authenticated the client. |
| DigestAuthenticationSid |
Indicates a SID present when the Microsoft Digest authentication package authenticated the client. |
| SChannelAuthenticationSid |
Indicates a SID present when the Secure Channel (SSL/TLS) authentication package authenticated the client. |
| ThisOrganizationSid |
Indicates a SID present when the user authenticated from within the forest or across a trust that does not have the selective authentication option enabled. If this SID is present, then OtherOrganizationSid cannot be present. |
| OtherOrganizationSid |
Indicates a SID present when the user authenticated across a forest with the selective authentication option enabled. If this SID is present, then ThisOrganizationSid cannot be present. |
| BuiltinIncomingForestTrustBuildersSid |
Indicates a SID that allows a user to create incoming forest trusts. It is added to the token of users who are a member of the Incoming Forest Trust Builders built-in group in the root domain of the forest. |
| BuiltinPerformanceMonitoringUsersSid |
Indicates a SID that matches the group of users that have remote access to schedule logging of performance counters on this computer. |
| BuiltinPerformanceLoggingUsersSid |
Indicates a SID that matches the group of users that have remote access to monitor the computer. |
| BuiltinAuthorizationAccessSid |
Indicates a SID that matches the Windows Authorization Access group. |
| WinBuiltinTerminalServerLicenseServersSid |
Indicates a SID is present in a server that can issue Terminal Server licenses. |
| MaxDefined |
Indicates the maximum defined SID in the WellKnownSidType enumeration. |
BINGO!!! Parece que tenemos el SID del grupo de admnistradores del dominio (lo he marcado en rojo en la tabla anterior).
Ahora vamos a generar el SID del grupo de adminstradores del dominio y ya podemos volver a probar el método IsInRole:
WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.AccountDomainAdminsSid, null);
return wp.IsInRole(sid);
Ops! Nuestro gozo en un pozo… se necesita informar el segundo argumento del constructor para el SID del grupo:
DomainSid
¿Y que kkgrnn$# representa que es este identificador? Pues según pone en la ayuda del constructor, debe proporcionarse el SID del dominio para que el constructor pueda devolver algunos identificadores de WellKnownSidType, entre los cuales está el de los administradores del dominio.
Dicho de otro modo, o sabemos el SID de nuestro dominio o todo lo anterior no vale para nada… :-(
¿Y cómo podemos saber el SID de dominio? Después de buscar un ratito, lo único que he encontrado es una utilidad de consola llamada PsGetSid, que forma parte de las PSTools del inefable Mark Russinovich. Basta descargar esta utilidad y ejecutarla desde la consola de este modo para saber el SID de nuestro dominio (el nombre de dominio en formato “microsoft.com” o “net.volvo.com”):
Sin embargo, me niego a tener que hacer esto para saber el identificador del dominio. Así que vamos a probar si podemos recuperar esta propiedad del esquema de AD mediante un DirectoryEntry. Para ello utilizaremos la clase Domain:
Domain d = Domain.GetDomain(new
DirectoryContext(DirectoryContextType.Domain, getDomainName()));
using (DirectoryEntry de = d.GetDirectoryEntry())
{
byte[] domSid = (byte[])de.Properties["objectSid"].Value;
string sdomainSid = sIDtoString(domSid);
Console.WriteLine(sdomainSid);
}
Nota: Aquí necesitaremos dos funciones de apoyo, la primera nos devuelve el nombre del domino, y la segunda transforma el array de bits del SID en su representación textual:
public static string getDomainName()
{
return IPGlobalProperties.GetIPGlobalProperties().DomainName;
}
public static string sIDtoString(byte[] sidBinary)
{
SecurityIdentifier sid = new SecurityIdentifier(sidBinary, 0);
return sid.ToString();
}
A todo esto el valor de la variable sdomainSid es el esperado!!! :-D
Poniéndolo todo junto
Al igual que el alegre bandolero, también soy un fanático de los métodos extensores, así que vamos a encapsular todo esto en un método que extienda la clase WindowsIdentity. Aquí va todo el código junto:
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Net.NetworkInformation;
using System.Security.Principal;
namespace Alpha.Code
{
public static class SecurityExtensions
{
public static bool IsDomainAdmin (this WindowsIdentity identity)
{
Domain d = Domain.GetDomain(new
DirectoryContext(DirectoryContextType.Domain, getDomainName()));
using (DirectoryEntry de = d.GetDirectoryEntry())
{
byte[] bdomSid = (byte[])de.Properties["objectSid"].Value;
string sdomainSid = sIDtoString(bdomSid);
WindowsPrincipal wp = new WindowsPrincipal(identity);
SecurityIdentifier dsid = new SecurityIdentifier(sdomainSid);
SecurityIdentifier dasid = new SecurityIdentifier(
WellKnownSidType.AccountDomainAdminsSid, dsid);
return wp.IsInRole(dasid);
}
}
public static string getDomainName()
{
return IPGlobalProperties.GetIPGlobalProperties().DomainName;
}
public static string sIDtoString(byte[] sidBinary)
{
SecurityIdentifier sid = new SecurityIdentifier(sidBinary, 0);
return sid.ToString();
}
}
}
Y la forma de usarlo es tan sencilla como esto:
if (WindowsIdentity.GetCurrent().IsDomainAdmin())
{
//Acciones a realizar si el usuario es administrador de dominio...
}
Un saludo desde las frías tierras de Andorra :-)
Noviembre 2009