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

Dependencia componentes parte 2
Dependencia componentes parte 2

Artículos relacionados:

En el post anterior vimos la primera parte del módulo de seguridad donde aplicamos los principios de diseño orientado a objetos SOLID. Ahora agregaremos las funcionalidades de cambiar y resetear contraseñas siguiendo los lineamientos de diseño establecidos en el post anterior. Para mencionar nuevamente que hay cosas que pueden verse como sobre ingeniería pero se explicaran todas las decisiones de diseño que fueron tomadas, igual cualquier duda están los comentarios para poder resolverlas :). 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 (Continuación)

Para esta parte tenemos los siguientes requerimientos:

  • Permitir al usuario que cambie su contraseña.
  • La nueva contraseña debe cumplir con las siguientes validaciones:
    1. No puede ser igual a la contraseña anterior.
    2. Longitud mínima de 8 caracteres.
    3. Longitud máxima de 20 caracteres.
    4. Cumplir con la complejidad requerida: Una mayúscula y un número.
  • Enviar una notificación al correo del usuario indicando el cambio.
  • Permitir al usuario que resetear su contraseña.
  • Enviar una notificación al correo del usuario indicando su contraseña temporal.
  • La funcionalidad de envió de correos debe diseñarse de tal forma que permita agregar nuevas notificaciones sin alterar el código existente.
  • Para generar las contraseñas se debe usar el método Proceso005 del componente PasswordGeneratorZxy.dll de uso interno.

2. Diseño del core  (Continuación)

Recuerden todo empieza en este paso, comenzamos a diseñar primero la funcionalidad base (el core) y luego pasamos a diseñar las implementaciones.

2.1 Modelo

El modelo de estos nuevos requerimientos está formado por las siguientes clases:

public enum ErrorCambioContrasena
{
    LongitudMinimaInvalida,
    ComplejidadIncorrecta,
    ContrasenaRepetida,
}

public class ResultadoCambioContrasena
{
    public bool ContrasenaActualizada { get; set; }
    public ErrorCambioContrasena Respuesta { get; set; }
}
2.2 Interfaz de notificación
public interface INotificacion
{
    string CorreoPara { get; set; }
    string CorreoDe { get; set; }
    string Asunto { get; set; }
}

public class NotificacionCambioContrasena : INotificacion
{
    public string CorreoPara { get; set; }
    public string CorreoDe { get; set; }
    public string Asunto { get; set; }
    public Usuario Usuario { get; set; }
}

public class NotificacionReseteoContrasena : INotificacion
{
    public string CorreoPara { get; set; }
    public string CorreoDe { get; set; }
    public string Asunto { get; set; }        
    public Usuario Usuario { get; set; }
}
2.3 Interfaz de generación de contraseña
public interface IServicioGeneracionContrasena
{
    string Generar();
}
2.4 Interfaz servicio de notificación
public interface IServicioNotificacion<in T> where T: INotificacion
{
    void Enviar(T parametroNotificacion);
}
2.5 Interfaz de Repositorio de usuarios
public interface IRepositorioComandoUsuario
{
    void ActualizarContrasena(string id, string nuevaContraseña);
}
2.6 Interfaz del servicio de actualización de contraseña

Este servicio tiene como  responsabilidad actualizar la contraseña (Cambiar o Resetear) de los usuarios del sistema.

public interface IServicioCambioContrasena
{
    ResultadoCambioContrasena Cambiar(string idUsuario, string nuevaContrasena, 
IServicioNotificacion<NotificacionCambioContrasena> servicioNotificacion);

    void Resetear(string idUsuario, 
IServicioNotificacion<NotificacionReseteoContrasena> servicioNotificacion);
}
2.7 Implementación del servicio de cambio de contraseña

Esta clase recibe las dependencias externas usando la inyección de dependencias (dependency Injection) por constructor y  por propiedad para el caso de ServicioGeneracionContraseña ya que solo el método Resetar lo utiliza.

public class ServicioCambioContrasena : IServicioCambioContrasena
{
    private readonly IRepositorioConsultaUsuario _repositorioConsultaUsuario;
    private readonly ICriptografia _criptografia;
    private readonly IRepositorioComandoUsuario _repositorioComandoUsuario;

    public IServicioGeneracionContrasena ServicioGeneracionContrasena { get; set; }

    public ServicioCambioContrasena(
        IRepositorioConsultaUsuario repositorioConsultaUsuario, 
        ICriptografia criptografia,  
        IRepositorioComandoUsuario repositorioComandoUsuario)
    {
        _repositorioConsultaUsuario = repositorioConsultaUsuario;
        _criptografia = criptografia;
        _repositorioComandoUsuario = repositorioComandoUsuario;
    }
    const int LongitudMinimaContrasena = 8;
    const string FormatoExpresionRegularContrasena = 
                 @&quot;^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{8,20}$&quot;;
}
 2.7.1 Métodos de validación
private bool ValidarContrasenaTieneLongitudMinima(string nuevaContrasena)
{
    return nuevaContrasena.Length &gt;= LongitudMinimaContrasena;
}

private bool ValidarContrasenaEsRepetida(Usuario usuario, string nuevaContrasena)
{
        string nuevaContrasenaCifrada = 
             _criptografia.Cifrar(nuevaContrasena);

        return nuevaContrasenaCifrada.Equals(usuario.Contrasena);
}

private bool ValidarContrasenaEsCompleja(string nuevaContrasena)
{
    var regex = new Regex(FormatoExpresionRegularContrasena);
    return regex.IsMatch(nuevaContrasena);
}
 2.7.2 Buscar usuario
private Usuario BuscarUsuario(string idUsuario)
{
    Usuario usuario = _repositorioConsultaUsuario.ObtenerPorId(idUsuario);
    if (usuario == null)
    {
        throw new ApplicationException("El usuario no existe");
    }
    return usuario;
}

Nota: En este caso se lanza una excepción porque se considera un escenario no contemplado que envíen un identificador de usuario que no existe. Esto es algo que no debería ocurrir.

2.7.3 Crear respuesta de validación
private ResultadoCambioContrasena 
    CrearRespuestaCambioContrasenaInvalida(ErrorCambioContrasena error)
{
    return new ResultadoCambioContrasena
    {
        ContrasenaActualizada = false,
        Respuesta = error
    };
}
2.7.4 Actualizar contraseña
private void ActualizarContrasena(string idUsuario, string nuevaContrasena)
{
string nuevaContrasenaCifrada = _criptografia.Cifrar(nuevaContrasena);

_repositorioComandoUsuario.ActualizarContrasena(idUsuario, nuevaContrasenaCifrada);
}
2.7.5 Método Resetear contraseña

Este método recibe la dependencia externa usando la inyección de dependencias (dependency Injection) por parámetro debido a que solo este método lo usa.

public void Resetear(string idUsuario, 
     IServicioNotificacion<NotificacionReseteoContrasena> servicioNotificacion)
{
    Usuario usuario = BuscarUsuario(idUsuario);

    string nuevaContrasena = ServicioGeneracionContrasena.Generar();
            
    ActualizarContrasena(idUsuario, nuevaContrasena);

    servicioNotificacion.Enviar(
        new NotificacionReseteoContrasena{Usuario = usuario});
}
2.7.6 Método Cambiar contraseña

Este método también usa la inyección de dependencias (dependency Injection) por parámetro

public ResultadoCambioContrasena Cambiar(string idUsuario, 
         string nuevaContrasena,
         IServicioNotificacion<NotificacionCambioContrasena> servicioNotificacion)
{
    Usuario usuario = BuscarUsuario(idUsuario);

    if (ValidarContrasenaEsRepetida(usuario, nuevaContrasena))
    {
        return
            CrearRespuestaCambioContrasenaInvalida(ErrorCambioContrasena.ContrasenaRepetida);
    }

    if (ValidarContrasenaTieneLongitudMinima(nuevaContrasena))
    {
        return 
            CrearRespuestaCambioContrasenaInvalida(ErrorCambioContrasena.LongitudMinimaInvalida);
    }
            
    if (ValidarContrasenaEsCompleja(nuevaContrasena))
    {
        return 
            CrearRespuestaCambioContrasenaInvalida(ErrorCambioContrasena.ComplejidadIncorrecta);
    }

    ActualizarContrasena(idUsuario, nuevaContrasena);

    servicioNotificacion.Enviar(new NotificacionCambioContrasena{Usuario = usuario});

    return new ResultadoCambioContrasena { ContrasenaActualizada = true };
}

3. Diseño de elementos de infraestructura

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

3.1 IServicioNotificacion

Para dar solución al requerimiento de envió de notificaciones se decidió usar el patrón Template Method. En cada notificación lo único que cambia es la forma en que se arma el formato del correo, por eso se decidió que cada sub clase debe definir el formato que necesita sin tocar el código principal.

3.1.1 Servicio notificación base
public abstract class ServicioNotificacionBase<T> : 
    IServicioNotificacion<T> where T:INotificacion
{
    protected abstract string CrearMensajeNotificacion(T parametroNotificacion);

    public void Enviar(T parametroNotificacion)
    {
        using (var smtpServer = new SmtpClient())
        {
            var mail = new MailMessage
            {
                From = new MailAddress(parametroNotificacion.CorreoDe)
            };

            mail.To.Add(parametroNotificacion.CorreoPara);

            mail.Subject = parametroNotificacion.Asunto;

            mail.Body = CrearMensajeNotificacion(parametroNotificacion);
            mail.IsBodyHtml = true;

            smtpServer.Send(mail);
        }
    }
}
3.1.2  Servicio notificación Cambio contraseña
public class ServicioNotificacioncambioContrasena : 
    ServicioNotificacionBase<NotificacionCambioContrasena>
{
    protected override string CrearMensajeNotificacion(
        NotificacionCambioContrasena parametroNotificacion)
    {
        //Codigo
    }
}
3.1.3 Servicio notificación Resetear contraseña
public class ServicioNotificacionReseteoContrasena : 
    ServicioNotificacionBase<NotificacionReseteoContrasena>
{
    protected override string CrearMensajeNotificacion(
        NotificacionReseteoContrasena parametroNotificacion)
    {
        //Codigo
    }
}
3.2 IServicioGeneracionContrasena

Para usar el componente externo PasswordGeneratorZxy.dll vamos a usar el patrón Adapter.  Al usar este patrón tenemos que definir una interfaz amigable con la que toda nuestra solución va a trabajar. La clase ServicioGeneracionContrasenaRng será la única que esta acoplada a este componente externo, si más adelante cambia la forma en cómo se generan las contraseñas el código exterior no se verá afectado.

public class ServicioGeneracionContrasenaRng 
       : IServicioGeneracionContrasena
{

    public string Generar()
    {
        var generador = new PasswordGeneratorZxy();
        //Codigo
        return generador.Proceso005();
    }
}
3.3 Repositorio Usuario

En el caso del repositorio de usuario contamos con dos interfaces: IRepositorioComandoUsuario e IRepositorioConsultaUsuario. La primera interfaz se encarga de ejecutar comandos sobre el repositorio de usuarios (insert, update, delete) y la otra interfaz se encarga de recuperar información del usuario (principio Interface Segregation)

3.3.1 IRepositorioComandoUsuario
public class RepositorioComandoUsuarioActiveDirectory : IRepositorioComandoUsuario
{
    public void ActualizarContrasena(string id, string nuevaContraseña)
    {
    }
}
3.3.1 IRepositorioCosultaUsuario
public class RepositorioCosultaUsuarioActiveDirectory : IRepositorioConsultaUsuario
{
    public Usuario ObtenerPorNombre(string nombreUsuario)
    {
    }

    public Usuario ObtenerPorId(string id)
    {
    }
}

4. Llamada desde el cliente

var critografia = new CriptografiaAes();
var repositorioConsulta = 
                    new RepositorioConsultaUsuarioActiveDirectory();
var repositorioComando = 
                     new RepositorioComandoUsuarioActiveDirectory();

var servicioCambioContrasena =
    new ServicioCambioContrasena(
        repositorioConsulta, critografia,
        repositorioComando);

servicioCambioContrasena.Cambiar(string.Empty, string.Empty,
    new ServicioNotificacioncambioContrasena());

servicioCambioContrasena.ServicioGeneracionContrasena = 
                     new ServicioGeneracionContrasenaRng();
            
servicioCambioContrasena.Resetear(string.Empty,
    new ServicioNotificacionReseteoContrasena());

Conclusión

Depende de las necesidades del sistema para saber en qué momento debemos aplicar los principios SOLID. En este ejemplo aplicamos los principios: Single Responsability, Open Closed, Liskov Substitution, Interface Segregation y Dependency Inversion. Además, de los patrones de diseño: Adapter y Template Method.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción X de la banda X Japan de Japón, les comparto el enlace.

Anuncios

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

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