c# Principios SOLID – Caso práctico: Módulo de seguridad Parte 1

Dependencia componentes
Dependencia componentes

Artículos relacionados:

Luego de haber conocido los 5 principios de diseño orientado a objetos SOLID creo que es necesario ver un ejemplo para reforzar toda esta teoría. Quiero mencionar que el caso que veremos a continuación es algo puntual y puede resultar exagerado en algunas decisiones de diseño, por el tamaño del ejemplo puede verse como sobre ingeniería. Para evitar que surjan preguntas explicare porque decidí aplicar cada decisión tomada en el diseño de los requerimientos. Finalmente, el objetivo de escribir este tipo de post es que los lectores de este blog aporten mejoras que encuentren en el código para compartirla con todos los interesados y generar mejores ideas de diseño… así que manos a la obra :).

Para descargar el código haz clic acá.

1. Escenario: Módulo de seguridad

Para esta parte tenemos los siguientes requerimientos:

  • Crear una funcionalidad para autenticar usuarios (Usando nombre y contraseña).
  • La funcionalidad para autenticar debe indicar lo siguiente:
    1. Si el usuario o contraseña es incorrecto.
    2. Si el usuario está bloqueado.
    3. Si la contraseña expiro (15 días desde el último cambio).
    4. Si el usuario se pudo autenticar con éxito.
  • La contraseña se debe almacenar cifrada usando el algoritmo AES o TripleDes dependiendo de la configuración del aplicativo.
  • La validación de credenciales se realiza contra una base de datos Sql, pero debe soportar, para el año entrante, validar las credenciales contra el directorio activo.
  • Cada vez que se use la funcionalidad para autenticar se debe registrar el usuario, la fecha y la hora en que ocurrió el evento en un archivo de texto.

2. Diseño del core (módulos de alto nivel)

Aplicamos lo que indica el principio Dependency Inversion: comenzamos a diseñar primero la funcionalidad base (el core) y luego pasamos a diseñar las implementaciones. En este punto no debemos preocuparnos por ningún tipo de dependencia externo, lo que nos importa acá es definir el core.

2.1 Modelo

El modelo está formado por las siguientes clases:

public class Credencial
{
    public string Usuario { get; set; }
    public string Contrasena { get; set; }
}

public class Usuario
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public string Contrasena { get; set; }
    public DateTime FechaUltimoCambioContrasena { get; set; }
    public bool Bloqueado { get; set; }
}

public enum ErrorAutenticacion
{
    CredencialesIncorrectas,
    UsuarioBloqueado,
    ContrasenaExpiro
}

public class ResultadoAutenticacion
{
    public bool Autenticado { get; set; }
    public ErrorAutenticacion Respuesta { get; set; }
    public Usuario Usuario { get; set; }
}
2.2 Interfaz del servicio de autenticación

Este servicio tiene como única responsabilidad autenticar a los usuarios del sistema (recuerden el principio Single Responsability).

public interface IServicioAutenticacion
{
    ResultadoAutenticacion AutenticarUsuario(Credencial credencial);
}
2.3 Interfaz de Repositorio de usuarios
public interface IRepositorioConsultaUsuario
{
    Usuario ObtenerPorNombre(string nombreUsuario);
}
2.4 Interfaz de Criptografía
public interface ICriptografia
{
    string Cifrar(string textoPlano);
}
2.5 Implementación del servicio de autenticación

Esta clase recibe las dependencias externas usando la inyección de dependencias (dependency Injection) por constructor.

public class ServicioAutenticacion : IServicioAutenticacion
{
    private readonly 
        IRepositorioConsultaUsuario _repositorioConsultaUsuario;

    private readonly
        ICriptografia _criptografia;

    public ServicioAutenticacion(
        IRepositorioConsultaUsuario repositorioConsultaUsuario, 
        ICriptografia criptografia)
    {
        _repositorioConsultaUsuario = repositorioConsultaUsuario;
        _criptografia = criptografia;
    }
}
2.5.1 Métodos de validación
private bool ValidarContrasenaEsInvalida(
                    Usuario usuario, Credencial credencial)
{
    string credencialCifrada = 
                 _criptografia.Cifrar(credencial.Contrasena);

    return !credencialCifrada.Equals(usuario.Contrasena);
}

public bool ValidarContrasenaSiExpiro(Usuario usuario)
{
    int nroDiasUltimoCambioContrasena =
        DateTime.Now.Subtract(usuario.FechaUltimoCambioContrasena).Days;
    return nroDiasUltimoCambioContrasena > DiasMaximoCambioContrasena;
}
2.5.2 Crear respuesta de validación
private ResultadoAutenticacion 
         CrearRespuestaAutenicacionInvalida(ErrorAutenticacion error)
{
    return new ResultadoAutenticacion
    {
        Autenticado =  false,
        Respuesta = error
    };
}
2.5.3 Método Autenticar
const int DiasMaximoCambioContrasena = 15;

public ResultadoAutenticacion AutenticarUsuario(Credencial credencial)
{
    Usuario usuario = 
        _repositorioConsultaUsuario.ObtenerPorNombre(credencial.Usuario);

    if (usuario == null)
    {
        return 
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.CredencialesIncorrectas);
    }

    if (ValidarContrasenaEsInvalida(usuario, credencial))
    {
        return
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.CredencialesIncorrectas);
    }
            
    if (usuario.Bloqueado)
    {
        return
            CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.UsuarioBloqueado);
    }
            
    return ValidarContrasenaSiExpiro(usuario) ? 
        CrearRespuestaAutenicacionInvalida(ErrorAutenticacion.ContrasenaExpiro) : 
        new ResultadoAutenticacion { Autenticado = true, Usuario = usuario };
}

3. Diseño de elementos de infraestructura

Una vez detectada las dependencias que necesita el core pasamos a definir estas implementaciones.

3.1 ICriptografia

El requerimiento pide que se cifren las contraseñas usando el algoritmo AES.

public class CriptografiaAes : ICriptografia
{
    public string Cifrar(string textoPlano)
    {
        //Codigo
    }
}

También requiere que soporte el uso del algoritmo TripleDes.

public class CriptografiaTripleDes : ICriptografia
{
    public string Cifrar(string textoPlano)
    {
        //Codigo
    }
}

Para dar solución al tema de intercambio de algoritmos de cifrado se usa el patrón Strategy y el principio Open Closed. Aplicamos el patrón Strategy porque permite hacer cambio entre los dos algoritmos requeridos, ya que son de la misma familia. También aplicamos el principio Open Closed porque cada vez que se quiera agregar un nuevo algoritmo de cifrado no se tiene que tocar las clases existentes sino crear una nueva.

3.2 IRepositorioConsultaUsuario
public class RepositorioConsultaUsuarioSql : IRepositorioConsultaUsuario
{
    public Usuario ObtenerPorNombre(string nombreUsuario)
    {
        //Codigo
    }
}

4. Diseño Servicio autenticación  decorador Log

Para dividir las responsabilidades entre la lógica de la autenticación y el registro del log se decidió implementar el patrón Decorator. Esta nueva clase decora con la funcionalidad del registro de log a la clase ServicioAutenticacion.

4.1 Interfaz Log de mensajes
public interface ILog
{
    void RegistrarMensaje(string mensaje);
}
4.2 Implementación del decorador
public class ServicioAutenticacionDecoradorLog : IServicioAutenticacion
{
    private readonly IServicioAutenticacion _servicioAutenticacion;
    private readonly ILog _log;

    public ServicioAutenticacionDecoradorLog(
            IServicioAutenticacion servicioAutenticacion, 
            ILog log)
    {
        _servicioAutenticacion = servicioAutenticacion;
        _log = log;
    }
4.2.1 Método registro de mensaje
private void RegistrarLogAutenticar(Credencial credencial)
{
    string mensaje =
        string.Format("FechaHora:{0} - Usuario:{1}", DateTime.Now, credencial.Usuario);
    _log.RegistrarMensaje(mensaje);

}
4.2.2 Método Autenticar
public ResultadoAutenticacion AutenticarUsuario(Credencial credencial)
{
    RegistrarLogAutenticar(credencial);
    return _servicioAutenticacion.AutenticarUsuario(credencial);
}

5. Llamada desde el cliente

5.1 ILog con archivo de texto
public class LogArchivoTexto : ILog
{
    public void RegistrarMensaje(string mensaje)
    {
    }
}
5.2 Autenticación contra Sql
ICriptografia critografia = 
       new CriptografiaTripleDes();
IRepositorioConsultaUsuario repositorioConsulta = 
        new RepositorioConsultaUsuarioSql();
ILog log = new LogArchivo();
var servicioAutenticacionCore =
    new ServicioAutenticacion(repositorioConsulta, critografia);

var servicioAutenticacionLog =
    new ServicioAutenticacionDecoradorLog(servicioAutenticacionCore, log);

servicioAutenticacionLog.AutenticarUsuario(new Credencial());
5.3 IRepositorioConsultaUsuario con Active directory

Ahora se necesita buscar a los usuarios en el Active directory.

public class RepositorioConsultaUsuarioActiveDirectory : IRepositorioConsultaUsuario
{
    public Usuario ObtenerPorNombre(string nombreUsuario)
    {
        //Codigo
    }
}
5.4 Autenticación contra el ActiveDirectory

Para cambiar el tipo de repositorio solo debemos pasar como referencia la clase RepositorioConsultaUsuarioActiveDirectory. Al intercambiar objetos de la misma clase base sin alterar el funcionamiento del código se aplicando el principio Liskov Substitution.

ICriptografia critografia = 
       new CriptografiaTripleDes();
IRepositorioConsultaUsuario repositorioConsulta = 
        new RepositorioConsultaUsuarioActiveDirectory();
ILog log = new LogArchivo();
var servicioAutenticacionCore =
    new ServicioAutenticacion(repositorioConsulta, critografia);

var servicioAutenticacionLog =
    new ServicioAutenticacionDecoradorLog(servicioAutenticacionCore, log);

servicioAutenticacionLog.AutenticarUsuario(new Credencial());

Conclusión

Aplicar los principios SOLID no es nada sencillo, pero recuerden la practica hace al maestro. En este ejemplo aplicamos los principios: Single Responsability, Open Closed, Liskov Substitution y Dependency Inversion. Además, de los patrones de diseño: Strategy y Decorator.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción Brutalidad de la banda M.A.S.A.C.R.E una de las mejores bandas de metal de Perú, les comparto el enlace.

Anuncios

3 comentarios en “c# Principios SOLID – Caso práctico: Módulo de seguridad Parte 1

  1. Muy buen post Alex, luego de leer tu serie de artículos me nace una gran duda y es;

    ¿Cómo debe quedar el modelo de tu base de datos?, ¿Los principios SOLID deben venir de tu BD al diseño de tus clases o es lo contrario? ¿O tal vez son totalmente distintos?

    Te agradecería si me pudieras quitar esa duda, ya que es dificil encontrar información con buen contenido como el que tu ofreces.

    Nuevamente Felicidades por tu blog!, tus post son geniales!

    @Saludos! 🙂

    Me gusta

    1. Hola Erick, el core del sistema no debe ser guiado por la base de datos, pero esto no quiere decir que modelemos la base de datos de otra forma (SOLID no se aplica a modelos relacionales ;)). Bajo este enfoque la base de datos es un tipo de dependencia mas como lo son los: servicios web, sistemas de archivos, etc. Lo mas importante es diseñar el modelo,lo que necesita el negocio y luego vemos de donde se recupera esa información. Como veras en el ejemplo esa interfaz IRepositorioConsultaUsuario no esta amarrado a la base de datos si deseo puedo recuperar información de un servicio web sin que mi corte tenga que cambiar por está modificación. Muchas gracias 🙂 por el comentario

      Me gusta

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