AccountManagement, aislamiento de FTP y acceso conmutado

A primera vista nada tienen que ver las tres partes que conforman el título de este comentario.  El pegamento que permite su fusión es Active Directory (AD).  Anteriormente mostré como acceder a las propiedades de un usuario de AD no expuestas por UserPrincipal a través del método GetUnderlyingObject.  Aquí muestro la forma “elegante” recomendada en la documentación, que consiste en crear una nueva clase personalizada que herede de UserPrincipal y que exponga las propiedades requeridas por nuestra aplicación.  Esto lo haré a través de un ejemplo práctico que utiliza propiedades poco documentadas de AD que lo vinculan con dos servicios bien conocidos: FTP y acceso conmutado (acceso teléfonico a redes, dialup).

Retomemos el ejemplo del usuario del comentario anterior, al cual se le debe dar acceso FTP al sitio web institucional para su actualización.  Olvidaba decir que nuestro sitio es muy dinámico pues mostramos información sobre competencias deportivas que tienen lugar tanto en horario diurno como nocturno.  Debido a ello al usuario se le ha otorgado un acceso conmutado a la red para que pueda hacer su trabajo fuera de la oficina.  Pero en aras de proteger la integridad del sitio se requiere que solo pueda conectarse desde el teléfono de su casa o desde su oficina en el estadio.  El acceso conmutado se autentifica a través del servicio Internet Authentication Service (IAS) de Windows.

Para el FTP recordemos que a partir de IIS 6.0 se introduce el aislamiento de las cuentas de usuario como una opción que proporciona mayor seguridad para el servicio y a través de la cual los usuarios solo pueden visualizar sus carpetas personales.  Si el aislamiento se instrumenta usando AD entonces la flexibilidad es mucho mayor pues se pueden asignar carpetas personales incluso en carpetas compartidas en otros servidores.  La asignación de la carpeta se realiza a través de dos propiedades de AD para el usuario en cuestión: msIIS-FTPRoot y msIIS-FTPDir; la concatenación de ambas resulta en la carpeta correspondiente al usuario.  Por ejemplo, si el sitio está en E:\WWWRoot, podemos poner msIIS-FTPRoot = E:  y msIIS-FTPDir = \WWWRoot.

Para el requerimiento de limitar la conexión a dos números de teléfono, debemos asignar dos propiedades multivaluadas de AD: msNPSavedCallingStationID y msNPCallingStationID.  No he logrado comprender por qué, pero Microsoft  insiste en que no se modifiquen estas propiedades manualmente, que son usadas internamente.  Sin embargo a través de cuidadosa experimentación se puede comprobar que fijando valores iguales (la lista de números de teléfono permitidos) para las dos, se logra el efecto deseado.

En ambos casos (FTP e IAS) se torna complicado para los administradores establecer los valores de las propiedades correspondientes porque no es posible acceder a ellas desde la consola de administración de Active Directory Users and Computers.  Deben ser fijadas a través de programación.  Es posible usar scripts para modificar las propiedades de acceso conmutado (dialup) e IIS ofrece también un script (iisftp.vbs) para el FTP.  Sin embargo, cuando la cantidad de usuarios crece, se torna tedioso usar los scripts; una solución más efectiva sería dotar al personal de soporte técnico de una pequeña aplicación ASP.NET que les permita “Activar FTP al usuario ‘fulano’ con Raíz en ‘tal carpeta’ y Directorio en ‘mas cual carpeta'” y “Activar acceso conmutado al usuario ‘fulano’ desde los números de teléfono ‘123’, ‘456’, …”.

La siguiente clase extiende a UserPrincipal con tres propiedades públicas: conmutadoTelefonosPermitidos, ftpRoot y ftpDir; y dos propiedades protegidas: savedCallingStationId y callingStationId, que son utilizadas por conmutadoTelefonosPermitidos.

using System;
using System.Linq;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Collections.Generic;

[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
public class Usuario:UserPrincipal
{
    public Usuario(PrincipalContext context)
        :base(context)
    {
    }

    public Usuario(PrincipalContext context, string samAccountName, string password, bool enabled)
        :base(context, samAccountName, password, enabled)
    {
    }

    public static new Usuario FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
    {
        return (Usuario) FindByIdentityWithType(context, typeof(Usuario), identityType, identityValue);
    }

    [DirectoryProperty("msIIS-FTPRoot")]
    public string ftpRoot
    {
        get
        {
            object[] result = this.ExtensionGet("msIIS-FTPRoot");
            if (result.Length==1)
                return (string)result[0];
            else
                return null;
        }
        set
        {
            if (value == "" ) value = null;
            this.ExtensionSet("msIIS-FTPRoot", value);
        }
    }

    [DirectoryProperty("msIIS-FTPRoot")]
    public string ftpDir
    {
        get
        {
            object[] result = this.ExtensionGet("msIIS-FTPDir");
            if (result.Length == 1)
                return (string)result[0];
            else
                return null;
        }
        set
        {
            if (value == "" ) value = null;
            this.ExtensionSet("msIIS-FTPDir", value);
        }
    }

    [DirectoryProperty("msNPSavedCallingStationID")]
    protected string[] savedCallingStationId
    {
        get
        {
            string[] valores;
            object[] valoresExtraidos = this.ExtensionGet("msNPSavedCallingStationID");
            int cantidad = valoresExtraidos.Length;
            valores = new string[cantidad];
            if (valoresExtraidos.Length > 0)
            {
                for (int i = 0; i < cantidad; i++)
                    valores&#91;i&#93; = (string)valoresExtraidos&#91;i&#93;;

            }
            return valores;
        }
        set
        {
            this.ExtensionSet("msNPSavedCallingStationID", value);
        }
    }

    &#91;DirectoryProperty("msNPCallingStationID")&#93;
    protected string&#91;&#93; callingStationId
    {
        get
        {
            string&#91;&#93; valores;
            object&#91;&#93; valoresExtraidos = this.ExtensionGet("msNPCallingStationID");
            int cantidad = valoresExtraidos.Length;
            valores = new string&#91;cantidad&#93;;
            if (valoresExtraidos.Length > 0)
            {
                for (int i = 0; i < cantidad; i++)
                    valores&#91;i&#93; = (string)valoresExtraidos&#91;i&#93;;

            }
            return valores;
        }
        set
        {
            this.ExtensionSet("msNPCallingStationID", value);
        }
    }

    public List<string> conmutadoTelefonosPermitidos
    {
        get
        {
            string[] saved = savedCallingStationId;
            if (saved == null) new List<string>();
            return saved.ToList<string>();
        }
        set
        {
            if (value.Count == 0)
            {
                callingStationId = savedCallingStationId = null;
            }
            else
            {
                callingStationId = savedCallingStationId = value.ToArray();
            }
        }
    }

}

Para usarla y fijar algunas propiedades:

        PrincipalContext contextoDominio = new PrincipalContext(ContextType.Domain, "dc1.centrodeportes.com", "usuario_privilegiado", "clave_usuario_privilegiado");
        Usuario user = Usuario.FindByIdentity(contextoDominio, IdentityType.SamAccountName, "johnbarquin");

        string[] tels = { "555555", "555556" };
        List<string> telefonos = tels.ToList<string>();
        user.conmutadoTelefonosPermitidos = telefonos;

        user.ftpRoot = txtftproot.Text.Trim();
        user.ftpDir = txtftpdir.Text.Trim();
        user.Save();

Nótese que las propiedades de acceso conmutado se han instrumentando protegidas y se han unificado en una sola pública conmutadoTelefonosPermitidos que encapsula la condición que las dos propiedades originales deben tomar el mismo valor. Otro detalle importante es que para limpiar una propiedad se utiliza null en lugar de cadena vacía o lista vacía.

La comparación de este método con el original de Microsoft para fijar las propiedades de FTP, muestra que usando AccountManagement hemos ganado en claridad y sencillez.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: