| Herramientas
útiles para el manejo de la seguridad en ASP.NET 2.0 |
En muchas oportunidades me sucede que en distintas
consultorías surgen temas más “de fondo” que
específicamente técnicos, referidos a la seguridad
en las aplicaciones asp.net.
Quiero decir, hay mucho para hablar de seguridad,
amenazas, procedimientos de encriptación y todo
eso… pero a veces, los problemas son mucho más
simples, y no por ello menos importantes.
Por eso en esta oportunidad quiero presentarles dos
casos, totalmente prácticos.
El problema de la membresía
Parto de la base que ya saben que en Asp.Net 2.0
podemos manejar todo el tema de “pertenencia” y
autenticación en un sitio Web, utilizando los
objetos de membresía por medio de los controles
como Login, LoginView etc.
Y no sólo eso: desde el entorno de Visual Studio y
de Web Express, es posible administrar dicha
seguridad.
Sin embargo, cuando el sitio Web se publica en un
servidor de producción, dicha posibilidad
desaparece, a menos que instalen Visual Studio en
el servidor, práctica totalmente NO recomendada .
Pero en realidad, la información está allí: sólo es
necesario ir a buscarla.
Una clase para administrar la membresía.
Sencillamente, lo que necesitamos es obtener la
información desde el repositorio donde se
encuentre, cosa que perfectamente se podría hacer
consultando la base de datos. Sin embargo, nadie
nos promete que en el tiempo dicha información se
persista de la misma forma. Además, dado que es
posible utilizar otros orígenes de membresía, es
preferible guiarnos por lo más estándar.
Por otra parte, sabemos que en ASP.Net, lo más
fácil y rápido es utilizar controles vinculables a
datos. Entonces, siempre será preferible exponer en
nuestros métodos objetos capaces de ser
vinculables, como por ejemplo, listas que
implementen IList, o DataTable.
En este caso en particular, me voy a apoyar en un
par de tablas definidas manualmente.
|
Nota: en el código, todos los nombres de las
columnas, métodos, etc. Están en inglés, ya que les estoy
mostrando código que utilizo en la vida diaria… que a veces me
lleva a comunicarme con gente que no es hispano parlante. |
La tabla RolesTable contiene sólo una columna,
Name,
para contener los nombres de los roles existentes.
La tabla UsersTable contiene una columna Name, otra
User (para el nombre de usuario) y una más,
Select
de tipo Boolean, que nos permitiría en una página,
seleccionar cada elemento para, por ejemplo,
agregar dichos usuarios a un rol.
Entonces, en una clase, podemos exponer un método
que devuelva la lista de roles de nuestro sitio,
con el siguiente código:
Public Function
ListRoles()
As
RolesTable
Dim
t
As
New
RolesTable
For Each
s
As String
In
System.Web.Security.Roles.GetAllRoles
t.Rows.Add(s)
Next
Return
t
End Function
Obtener los usuarios que pertenecen a un rol,
sería:
Public Function
UsersInRole(ByVal role
As String)
As
UsersTable
Dim
t
As New
UsersTable
For Each
s
As String
In _
System.Web.Security.Roles.GetUsersInRole(role)
t.Rows.Add(role, s,
False)
Next
Return
t
End Function
Y los que no están:
Public Function
UsersNotInRole(ByVal
role
As String)
As
UsersTable
Dim
t
As New
UsersTable
For Each
s
As
System.Web.Security.MembershipUser
In
_
System.Web.Security.Membership.GetAllUsers
If Not _
System.Web.Security.Roles.IsUserInRole(s.UserName)
Then
t.Rows.Add(role,
s,
False)
End If
Next
Return
t
End Function
De la misma forma, es posible implementar métodos
sencillos para manipular los usuarios y roles.
Agregando una página que opere con esta clase, en
cada sitio, se dispondrá de una herramienta
sencilla para administrar adecuadamente el sitio.
Lo dicho: algo simple, pero que en muchos
casos facilita la vida.
La seguridad integrada y el Directorio Activo.
Una problemática distinta, plantean las Intranets.
En esos casos, la mayoría de las aplicaciones se
apoyan en la autenticación integrada y, si bien se
puede utilizar
System.Web.Security.ActiveDirectoryMembershipProvider
para administrar la membresía, es muy probable que
existan otras informaciones que se requieran desde
el Directorio Activo, que no estarán disponibles en
dicho espacio de nombres (Namespace).
Para trabajar adecuadamente con el Directorio
Activo, es necesario agregar referencias a System.
DirectoryServices, pero también a un componente COM,
que aparece en la lista como “Active DS Type
Library” (No pregunten, yo tampoco entiendo porqué
no está en .Net ).
Active DS es una cosa algo extraña para quien no
haya tenido que pelear duro en las primeras épocas
de COM: casi no existen clases, son todas
Interfaces, las cuales se deben asignar dependiendo
del tipo de dato (que hay que conocer previamente
para estar seguro), etc.
Tampoco los objetos tienen constructores (o sea, no
se puede hacer New de ellos): hay que “obtenerlos”,
casi mágicamente desde vaya uno a saber donde. Por
suerte, al menos funcionan.
En realidad, Active DS encapsula llamadas a bajo
nivel utilizando protocolo LDAP (http://www.ldap-es.org/node/21
), que es en lo que se basa todo el Directorio
Activo.
Investigando las características de un usuario.
Comencemos con una clase (AdServices), que nos
permita comenzar nuestra tarea. En dicha clase,
incluiremos el Active DS y crearemos una colección,
para poder preservar los Grupos del Directorio
Activo a los cuales pertenece un usuario:
Imports
ActiveDs
Public Class
AdServices
Dim
colGroups
As New
System.Collections.Specialized.StringCollection
Implementemos un constructor para esta clase, que
reciba el nombre del usuario como argumento.
Considerando un sitio con autenticación integrada,
dicho nombre estará definido con el formato <NombreDominio>\<nombreusuario>,
que es lo que se obtiene si se consulta Request.ServerVariables(“AUTH_USER”).
A partir de dicho valor, se debe primero establecer
comunicación con el dominio. Basados en el
protocolo LDAP, un dominio se consulta en el
espacio GC, por lo cual debemos partir de allí
Sub New(ByVal
logonName
As String)
'Get the slash position
Dim
pos
As Integer =
logonName.LastIndexOf("\")
'Get the Domain name
Dim
dom
As String =
logonName.Substring(0,
pos)
'Get the user name
Dim
sUser
As String =
logonName.Replace(dom &
"\", "")
'Create a Searcher over the doamin
Dim
searcher
As New _
DirectoryServices.DirectorySearcher(New _
DirectoryServices.DirectoryEntry( _
String.Format("GC://{0}", dom)))
Para poder obtener el usuario adecuadamente, es
necesario indicarle al objeto Searcher, que cargue
la propiedad sAMAccountName, que es la que
corresponde a la cuenta de Windows.
Luego de ello, se puede hacer la búsqueda por dicha
propiedad:
searcher.PropertiesToLoad.Add("sAMAccountName")
searcher.Filter =
String.Format("(sAMAccountName={0})",
sUser)
Dim
u
As
IADsUser =
CType(GetObject(searcher.FindOne.Path),
IADsUser)
Finalmente, una vez obtenido el usuario, podemos
cargar nuestra colección con los grupos a los
cuales pertenece:
For Each
g
As
IADsGroup
In
u.Groups
colGroups.Add(g.Name.Replace("CN=",
g.ADsPath))
Next
Como se ve, la propiedad ADsPath es la que contiene
el nombre, pero en todos los casos, ésta comienza
con “CN=” (CN por Canonical Name). Por ello, para
obtener los nombres de los grupos “limpios”,
utilizamos el Replace del objeto String.
Para terminar, les dejo aquí otro método de la
misma clase, para obtener los Miembros de un grupo
específico.
A partir de aquí, es resorte de su propia
investigación, expandir la funcionalidad de esta
clase.
Espero que les sea útil.
Public Function
MemebrsOf(ByVal
group
As String,
ByVal
domain
As String)
As String()
'Create the Searcher, specifing that it will search
ONLY groups
Dim
searcher
As New
DirectoryServices.DirectorySearcher( _
New
DirectoryServices.DirectoryEntry( _
String.Format("GC://{0}",
domain)), "(objectType=group)")
'Add a filter with the Group Name
searcher.Filter = String.Format("(name={0})",
group)
'Try to get the Group
Dim
g
As
IADsGroup =
CType(GetObject(searcher.FindOne.Path),
IADsGroup)
If
g
IsNot
Nothing
Then
Dim su As
New
System.Collections.Specialized.StringCollection
'Get the members of the group
g.Members.Filter
= "(objectType=user)"
For Each u2
As
IADs
In
g.Members
'Besides we define that the object type has to be
user, some other
members can appear.
'The Class property tell us exactly which kind of
object it is.
If u2.Class =
"user"
Then
Dim u
As
IADsUser =
CType(u2, IADsUser)
su.Add(u.Name)
End If
Next
Dim sArr(su.Count - 1)
As String
su.CopyTo(sArr, 0)
Return
sArr
Else
Throw New
Exception(String.Format("The group {0}
does not exist in these domain",
group))
End If
End Function

Nombre
Daniel Seara
Daniel
es uno de los mentores asociados a
Solid Quality Learning Iberoamérica.
Especialista en desarrollos .NET. Daniel se ha
convertido en desarrollador utilizando las herramientas de
Microsoft desde hace 13 años.
El ha sido una de las personas encargadas de
presentar e introducir nuevos productos y tecnologías de
Microsoft a la comunidad de Desarrolladores durante los
últimos 7 DevDays en Argentina y como orador en los
últimos 6 días del DevDays en Perú.,
Como director Regional del programa MSDN.
Adicionalmente ,ha participado en eventos regionales
organizados por
INETA,
como en el Tour Argentino de Septiembre 2003,
y el Tour Andino (Incluyendo Venezuela,
Colombia, Ecuador & Perú). Daniel
ha trabajado como entrenador Microsoft en los mas
importantes CTECs de Argentina.
Daniel además es propietario de NDSoft,
una compañía de consultoría y desarrollo,
dedicada a la aplicación de las más nuevas tecnologías en cada
proyecto, promocionando un mejor uso de los motores de Bases
de Datos y de las tecnologías .NET desde que estas han sido
lanzadas.
Daniel también es uno de los encargados
de Evangelizar sobre las tecnologías .NET en las comunidades
de Desarrolladores y trabaja junto a Microsoft para proveer
entrenamiento a las mejores compañías de desarrollo de
LatinoAmérica, Daniel es autor habitual de artículos y
contenidos técnicos para la
Universidad.Net.
|
|