English Русский 中文 Deutsch 日本語
preview
Creación de un Panel de administración de operaciones en MQL5 (Parte IV): Capa de seguridad de inicio de sesión

Creación de un Panel de administración de operaciones en MQL5 (Parte IV): Capa de seguridad de inicio de sesión

MetaTrader 5Ejemplos |
195 1
Clemence Benjamin
Clemence Benjamin

Tabla de contenidos:


Introducción

La seguridad es primordial en cualquier campo de especialización y no podemos permitirnos pasar por alto su importancia. Ante la amenaza persistente del acceso no autorizado, es crucial proteger nuestro Panel de administración de posibles intrusos. Si personas no autorizadas obtienen acceso, podrían manipular fácilmente el panel y poner en peligro nuestros esfuerzos de comunicación con la comunidad de radiodifusión. El objetivo principal de este sistema es facilitar una comunicación confiable y, si bien podemos mejorar la funcionalidad a nivel de Asesor Experto, los riesgos de intrusión siguen siendo significativos.

Un atacante que acceda al panel de control podría enviar mensajes engañosos a nuestros usuarios, causando confusión y dañando la reputación del administrador del sistema. Para mitigar estos riesgos, creo que es esencial implementar una capa de seguridad que restrinja el acceso a funciones clave sin las credenciales correctas. Este enfoque sencillo de la seguridad protege nuestro panel y también ayuda a mantener la integridad de nuestras comunicaciones y la confianza de nuestra comunidad.

Panel de inicio de sesión

Panel de inicio de sesión


Descripción general de la seguridad en MQL5

MQL5 ofrece una amplia gama de funciones de seguridad diseñadas para proteger tanto el código fuente como los archivos compilados (EX5), salvaguardando la propiedad intelectual y evitando el uso no autorizado. Los mecanismos clave incluyen el cifrado de archivos compilados, licencias basadas en cuentas y en tiempo e integración con DLL externas para protección adicional. La plataforma admite firmas digitales para verificar la autenticidad del código, mientras que MetaQuotes proporciona protección del código mediante compilación y ofuscación para disuadir la ingeniería inversa. Para los productos distribuidos a través del MQL5 Market, el cifrado adicional garantiza que solo los usuarios con licencia puedan acceder y utilizar el software, lo que establece un marco de seguridad sólido para los desarrolladores.

En 2012, Investeo, autor de MQL5, analizó diversos métodos para proteger los programas y el código MQL5, y compartió valiosos conocimientos sobre la implementación de técnicas como la protección con contraseña, la generación de claves, las licencias de cuenta única, la protección con límite de tiempo, las licencias remotas, el cifrado seguro de licencias y los métodos avanzados contra la descompilación. Su trabajo sirve como referencia fundamental para mejorar la seguridad del programa.

Objetivo del debate:

Reconociendo la importancia de la seguridad, nuestro objetivo es debatir la implementación de la protección con contraseña para acceder a las funciones del Panel de administración. Profundizaremos en las técnicas utilizadas para lograr los resultados mostrados en la imagen anterior, centrándonos en cómo podemos proteger el acceso de los usuarios de manera eficaz.

Áreas de seguridad que requieren atención en nuestro panel de administración:

A medida que nuestro programa evoluciona e incorpora nuevas funciones, reconocemos la creciente complejidad, especialmente para los desarrolladores novatos. Identificamos varias áreas clave de interés en materia de seguridad:

  • Acceso seguro al panel de administración:

Para garantizar que ningún usuario no autorizado pueda acceder al Panel de administración sin la contraseña correcta, implementamos protección con contraseña. El acceso no autorizado podría dar lugar a la difusión de mensajes no deseados a la comunidad de operadores que confían en la información proporcionada por los administradores. Es posible que se produzcan clics aleatorios en los botones rápidos sin intención real, por lo que es esencial disponer de un código de acceso seguro. Aunque muchas aplicaciones utilizan la autenticación de dos factores (2FA) para una verificación adicional, nuestro enfoque actual sigue centrado en implementar funciones de seguridad básicas, con planes de incorporar opciones más avanzadas a medida que avancemos.

  • Seguridad de los mensajes de la API de Telegram:

También priorizamos la seguridad de la comunicación a través de la API de Telegram introduciendo de forma segura el ID del chat y el token del bot durante el inicio del programa. Este enfoque garantiza que los datos confidenciales permanezcan protegidos en manos del usuario. Telegram emplea sólidas funciones de seguridad para proteger las comunicaciones de los usuarios, incluyendo la seguridad de la capa de transporte a través del protocolo MTProto para los chats estándar y el cifrado de extremo a extremo para los chats secretos. Además, Telegram admite 2FA, lo que permite a los usuarios administrar sesiones activas y mejorar la seguridad de la cuenta. Si bien los protocolos de seguridad de Telegram son sólidos, los usuarios también deben asegurarse de que sus dispositivos sean seguros, ya que los dispositivos comprometidos pueden socavar estas protecciones.


Breve resumen de la Parte III

En nuestra discusión anterior, abordamos la incorporación de métodos para la gestión de temas. Sin embargo, estábamos trabajando con archivos que están sujetos a cambios durante las actualizaciones de la plataforma MetaTrader 5. Cada vez que se lanza una actualización, se descarga e instala automáticamente al reiniciar. A continuación se muestra un fragmento de código que ilustra los errores que encontré cuando intenté compilarlo después de las actualizaciones.

'UpdateThemeColors' - undeclared identifier     Admin Panel .mq5        390     16
'darkTheme' - some operator expected    Admin Panel .mq5        390     34
'SetTextColor' - undeclared identifier  Admin Panel .mq5        397     14
'textColor' - some operator expected    Admin Panel .mq5        397     27
'SetBackgroundColor' - undeclared identifier    Admin Panel .mq5        398     14
'bgColor' - some operator expected      Admin Panel .mq5        398     33
'SetBorderColor' - undeclared identifier        Admin Panel .mq5        399     14
'borderColor' - some operator expected  Admin Panel .mq5        399     29
'SetTextColor' - undeclared identifier  Admin Panel .mq5        424     12
'textColor' - some operator expected    Admin Panel .mq5        424     25
'SetBackgroundColor' - undeclared identifier    Admin Panel .mq5        425     12
'bgColor' - some operator expected      Admin Panel .mq5        425     31
'SetBorderColor' - undeclared identifier        Admin Panel .mq5        426     12
'borderColor' - some operator expected  Admin Panel .mq5        426     27
14 errors, 1 warnings           15      2

Solución temporal

Para resolver el problema, es esencial comprender primero el origen del problema. Como se explicó anteriormente, las actualizaciones de la plataforma restablecen las bibliotecas que estábamos usando a sus estados predeterminados. En consecuencia, los métodos que implementamos para la gestión de temas ya no son válidos, por lo que ahora estamos encontrando errores. Para solucionar esto, debemos sobrescribir los archivos actualizados (Dialog.mqh, Edit.mqh y Button.mqh) con las versiones ampliadas que adjunté en el artículo anterior. Puede ubicar la carpeta para los archivos incluidos como se muestra en la imagen a continuación.

Localice la carpeta raíz para los archivos incluidos

Cómo localizar fácilmente la carpeta raíz de dialog.mqh

Solución permanente:

Podemos cambiar el nombre del archivo Dialog.mqh y otros archivos relacionados que utilicemos a Extended_Dialog.mqh y ajustar nuestro código en consecuencia, pero asegúrese de actualizar cualquier instrucción #include que haga referencia al nombre antiguo del archivo para reflejar el nuevo nombre. Además, debemos verificar si hay otras dependencias que puedan hacer referencia a él y actualizarlas según sea necesario. Después de realizar estos cambios, volvemos a compilar nuestro proyecto para identificar si hay errores potenciales y probamos exhaustivamente la funcionalidad para garantizar que todo funcione correctamente. Esto lo guardará por separado con el nuevo nombre pero conservará el archivo original.

Por ejemplo, si ya hemos guardado el archivo como Extended_Dialog.mqh, podemos navegar hasta nuestro Panel de administración y ajustar el código de la siguiente manera:

#include <Controls\Extended_Dialog.mqh>
#include <Controls\Extended_Button.mqh>
#include <Controls\Extended_Edit.mqh>
#include <Controls\Label.mqh>

Ventajas de ahorrar con un nombre diferente

Ofrece la posibilidad de adaptar la funcionalidad específicamente a sus necesidades añadiendo o ajustando características que no están presentes en la versión integrada. Esta personalización le permite crear una interfaz única que se adapta a sus necesidades. Además, el uso de nombres de archivo personalizados ayuda a evitar conflictos con bibliotecas integradas o bibliotecas de terceros, lo que reduce el riesgo de comportamientos inesperados debido a la superposición de nombres. Aislar sus mejoras en un archivo renombrado protege aún más sus personalizaciones para que no se vean afectadas por otras funciones integradas que podrían utilizar el cuadro de diálogo original, lo que garantiza que pueda desarrollar y mantener su proyecto sin interferencias de cambios externos.


Integración de la protección con contraseña en el panel de administración

En este proyecto, implementaremos una protección con contraseña condicional utilizando una contraseña de tipo cadena que puede incluir tanto letras como números, lo que aumenta la complejidad. Aunque un PIN de cuatro dígitos puede parecer simple, sigue siendo difícil de adivinar. En el panel de administración, utilizamos la clase Dialog para solicitar al usuario una contraseña al iniciar sesión, con condiciones establecidas para mostrar las funciones principales del panel solo después de introducir correctamente la contraseña.

A medida que continuamos desarrollando el programa del Panel de administración, nuestro objetivo principal es establecer una seguridad de inicio de sesión sólida para garantizar que solo los usuarios autorizados puedan acceder a las funciones administrativas confidenciales. Hemos reconocido la necesidad crítica de proteger nuestro sistema contra el acceso no autorizado y debatimos cómo implementar MQL5 para proteger nuestros productos.

Mecanismo de autenticación

Para proteger el panel de administración, implementamos un mecanismo de autenticación sencillo basado en contraseña que solicita a los usuarios una contraseña antes de concederles acceso a cualquier funcionalidad. Esta decisión refleja nuestro compromiso con la validación de la identidad del usuario como requisito previo para acceder a los componentes críticos del programa.

// Show authentication input dialog
bool ShowAuthenticationPrompt()
{
    if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300))
    {
        Print("Failed to create authentication dialog");
        return false;
    }
   
}

En la función ShowAuthenticationPrompt, diseñamos una interfaz fácil de usar que guía eficazmente a nuestros usuarios a través del proceso de autenticación. Al crear un cuadro de diálogo específico para la introducción de la contraseña, garantizamos que el punto de acceso principal al panel de administración siga siendo seguro y, al mismo tiempo, intuitivo.

Para facilitar la comprensión, he resumido el código para la creación de diálogos en el fragmento de código que aparece a continuación, junto con comentarios que explican cómo funciona. Si desea refrescar su memoria sobre ejes y coordenadas, consulte (Parte I).  

// This condition checks if the authentication object is created successfully
if (!authentication.Create(          // Function call to create authentication
    ChartID(),                       // Retrieve the ID of the current chart
    "Authentication",                // Label of the dialog window in this case it is 'Authentication'
    0,                               // Initial X position on the chart also X_1
    100,                             // Initial Y position on the chart also Y_1
    500,                             // Width of the authentication window also X_2
    300                              // Height of the authentication window also Y_2
))

Una vez establecido el cuadro de diálogo de autenticación, procedemos a organizar otros elementos de la interfaz de usuario de manera similar, aunque con valores diferentes. El proceso comienza con la creación de un cuadro de entrada de contraseña donde los usuarios pueden escribir sus credenciales, seguido de botones esenciales. En concreto, nos centramos en dos botones principales: el botón «Login» y el botón «Close». El botón «Login» se utiliza para enviar la contraseña introducida, mientras que el botón «Close» ofrece a los usuarios la opción de salir del cuadro de diálogo si no conocen la contraseña. A continuación se muestra un fragmento de código que ilustra la lógica para crear estos botones y la etiqueta de solicitud de contraseña.

 // Create password input
    if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95))
    {
        Print("Failed to create password input box");
        return false;
    }
    authentication.Add(passwordInputBox);

    // Create prompt label
    if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 20))
    {
        Print("Failed to create password prompt label");
        return false;
    }
    passwordPromptLabel.Text("Enter password: Access Admin Panel");
    authentication.Add(passwordPromptLabel);

    // Create login button
    if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140))
    {
        Print("Failed to create login button");
        return false;
    }
    loginButton.Text("Login");
    authentication.Add(loginButton);

    // Create close button for authentication
    if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) // Adjusted position
    {
        Print("Failed to create close button for authentication");
        return false;
    }
    closeAuthButton.Text("Close");
    authentication.Add(closeAuthButton);

    authentication.Show(); // Show the authentication dialog
    ChartRedraw(); // Redraw the chart to reflect changes

    return true; // Prompt shown successfully
}

Gestión de contraseñas

Actualmente, utilizamos una contraseña simple codificada para las pruebas iniciales, lo que nos permite crear prototipos de la funcionalidad rápidamente. Sin embargo, entendemos plenamente que este enfoque conlleva riesgos, como la vulnerabilidad a ataques de fuerza bruta si el código se ve comprometido.

// Default password for authentication
string Password = "2024";

Si bien reconocemos que el uso de una contraseña codificada agiliza nuestro desarrollo, necesitamos realizar una transición hacia una solución más segura en futuras actualizaciones, concretamente, implementar archivos de configuración cifrados o utilizar un sistema de gestión de cuentas de usuario más sofisticado para mejorar la seguridad.

Gestión de entradas del usuario

Para reforzar la seguridad, debemos asegurarnos de que el campo de introducción de la contraseña esté claramente definido en el cuadro de diálogo de autenticación. Al guiar a los usuarios para que introduzcan sus contraseñas y validar esos datos con la contraseña almacenada, nuestro objetivo es ofrecer una experiencia de inicio de sesión fluida y segura.
// Handle login button click
void OnLoginButtonClick()
{
    string enteredPassword = passwordInputBox.Text();
    if (enteredPassword == Password) // Check the entered password
    {
        authentication.Destroy(); // Hide the authentication dialog
        Print("Authentication successful.");
        adminPanel.Show(); // Show the admin panel after successful authentication
    }
    else
    {
        Print("Incorrect password. Please try again.");
        passwordInputBox.Text(""); // Clear the password input
    }
}

En la función OnLoginButtonClick, el programa comprueba si la contraseña introducida coincide con la contraseña almacenada. Tras completar con éxito el inicio de sesión, se ocultó el cuadro de diálogo de autenticación y se mostró el panel de administración al usuario. Si la contraseña es incorrecta, se borra el campo de entrada y se solicita al usuario que vuelva a intentarlo, lo que garantiza que comprenda claramente el proceso y se sienta seguro durante el inicio de sesión.

También tenemos un controlador para el botón «Close», que se encarga de la lógica de salida. Al hacer clic en este botón, se cierra el cuadro de diálogo de autenticación y se elimina por completo al experto del gráfico, lo que garantiza que no quede ningún acceso a las funciones de administración. Esta medida refuerza la seguridad y proporciona una vía de salida clara para los usuarios que decidan no continuar con el proceso de autenticación. Así es como se define el controlador:

//+------------------------------------------------------------------+
//| Handle close button click for authentication                     |
//+------------------------------------------------------------------+
void OnCloseAuthButtonClick()
{
    authentication.Destroy();
    ExpertRemove(); // Remove the expert if user closes the authentication dialog
    Print("Authentication dialog closed.");
}

En este controlador, el método authentication. Destroy() cierra eficazmente el cuadro de diálogo, mientras que ExpertRemove() garantiza que el asesor experto se elimine por completo de la vista, lo que mejora la seguridad general de la aplicación.

Totalmente incorporado al programa principal:

//+------------------------------------------------------------------+
//|                                             Admin Panel.mq5      |
//|                     Copyright 2024, Clemence Benjamin            |
//|     https://www.mql5.com/en/users/billionaire2024/seller         |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.19"

#include <Trade\Trade.mqh>
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Label.mqh>

// Input parameters
input string QuickMessage1 = "Updates";
input string QuickMessage2 = "Close all";
input string QuickMessage3 = "In deep profits";
input string QuickMessage4 = "Hold position";
input string QuickMessage5 = "Swing Entry";
input string QuickMessage6 = "Scalp Entry";
input string QuickMessage7 = "Book profit";
input string QuickMessage8 = "Invalid Signal";
input string InputChatId = "Enter Chat ID from Telegram bot API";
input string InputBotToken = "Enter BOT TOKEN from your Telegram bot";

// Global variables
CDialog adminPanel;
CDialog authentication; // Renamed from passwordPanel 
CButton sendButton, clearButton, changeFontButton, toggleThemeButton, loginButton, closeAuthButton;
CButton quickMessageButtons[8], minimizeButton, maximizeButton, closeButton;
CEdit inputBox, passwordInputBox;
CLabel charCounter, passwordPromptLabel;
bool minimized = false;
bool darkTheme = false;
int MAX_MESSAGE_LENGTH = 4096;
string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman", "Britannic Bold", "Dubai Medium", "Impact", "Ink Tree", "Brush Script MT"};
int currentFontIndex = 0;

// Default password for authentication
string Password = "2024";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    if (!ShowAuthenticationPrompt())
    {
        Print("Authorization failed. Exiting...");
        return INIT_FAILED; // Exit if the authorization fails
    }

    // Initialize the main admin panel
    if (!adminPanel.Create(ChartID(), "Admin Panel", 0, 30, 30, 500, 500))
    {
        Print("Failed to create admin panel dialog");
        return INIT_FAILED;
    }

    // Create controls for the admin panel
    if (!CreateControls())
    {
        Print("Control creation failed");
        return INIT_FAILED;
    }

    // Initially hide the admin panel
    adminPanel.Hide();
    
    Print("Initialization complete");
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Show authentication input dialog                                 |
//+------------------------------------------------------------------+
bool ShowAuthenticationPrompt()
{
    if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300))
    {
        Print("Failed to create authentication dialog");
        return false;
    }

    // Create password input
    if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95))
    {
        Print("Failed to create password input box");
        return false;
    }
    authentication.Add(passwordInputBox);

    // Create prompt label
    if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 20))
    {
        Print("Failed to create password prompt label");
        return false;
    }
    passwordPromptLabel.Text("Enter password: Access Admin Panel");
    authentication.Add(passwordPromptLabel);

    // Create login button
    if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140))
    {
        Print("Failed to create login button");
        return false;
    }
    loginButton.Text("Login");
    authentication.Add(loginButton);

    // Create close button for authentication
    if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) // Adjusted position
    {
        Print("Failed to create close button for authentication");
        return false;
    }
    closeAuthButton.Text("Close");
    authentication.Add(closeAuthButton);

    authentication.Show(); // Show the authentication dialog
    ChartRedraw(); // Redraw the chart to reflect changes

    return true; // Prompt shown successfully
}

//+------------------------------------------------------------------+
//| Handle chart events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    if (id == CHARTEVENT_OBJECT_CLICK)
    {
        // Handle button clicks inside the authentication dialog
        if (sparam == "LoginButton")
        {
            OnLoginButtonClick(); // Call the login button handler
        }
        else if (sparam == "CloseAuthButton") // Made sure this matches the ID
        {
            OnCloseAuthButtonClick(); // Call the close button handler
        }
        
        
    }
    
    switch (id)
    {
        case CHARTEVENT_OBJECT_CLICK:
            if (sparam == "SendButton") OnSendButtonClick();
            else if (sparam == "ClearButton") OnClearButtonClick();
            else if (sparam == "ChangeFontButton") OnChangeFontButtonClick();
            else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick();
            else if (sparam == "MinimizeButton") OnMinimizeButtonClick();
            else if (sparam == "MaximizeButton") OnMaximizeButtonClick();
            else if (sparam == "CloseButton") OnCloseButtonClick();
            else if (StringFind(sparam, "QuickMessageButton") != -1)
            {
                long index = StringToInteger(StringSubstr(sparam, 18));
                OnQuickMessageButtonClick(index - 1);
            }
            break;

        case CHARTEVENT_OBJECT_ENDEDIT:
            if (sparam == "InputBox") OnInputChange();
            break;
    }
}

//+------------------------------------------------------------------+
//| Handle login button click                                        |
//+------------------------------------------------------------------+
void OnLoginButtonClick()
{
    string enteredPassword = passwordInputBox.Text();
    if (enteredPassword == Password) // Check the entered password
    {
        authentication.Destroy(); // Hide the authentication dialog
        Print("Authentication successful.");
        adminPanel.Show(); // Show the admin panel after successful authentication
    }
    else
    {
        Print("Incorrect password. Please try again.");
        passwordInputBox.Text(""); // Clear the password input
    }
}

//+------------------------------------------------------------------+
//| Handle close button click for authentication                     |
//+------------------------------------------------------------------+
void OnCloseAuthButtonClick()
{
    authentication.Destroy();
    ExpertRemove(); // Remove the expert if user closes the authentication dialog
    Print("Authentication dialog closed.");
}

//+------------------------------------------------------------------+
//| Create necessary UI controls                                     |
//+------------------------------------------------------------------+
bool CreateControls()
{
    long chart_id = ChartID();

    // Create the input box
    if (!inputBox.Create(chart_id, "InputBox", 0, 5, 25, 460, 95))
    {
        Print("Failed to create input box");
        return false;
    }
    adminPanel.Add(inputBox);

    // Character counter
    if (!charCounter.Create(chart_id, "CharCounter", 0, 380, 5, 460, 25))
    {
        Print("Failed to create character counter");
        return false;
    }
    charCounter.Text("0/" + IntegerToString(MAX_MESSAGE_LENGTH));
    adminPanel.Add(charCounter);

    // Clear button
    if (!clearButton.Create(chart_id, "ClearButton", 0, 235, 95, 345, 125))
    {
        Print("Failed to create clear button");
        return false;
    }
    clearButton.Text("Clear");
    adminPanel.Add(clearButton);

    // Send button
    if (!sendButton.Create(chart_id, "SendButton", 0, 350, 95, 460, 125))
    {
        Print("Failed to create send button");
        return false;
    }
    sendButton.Text("Send");
    adminPanel.Add(sendButton);

    // Change font button
    if (!changeFontButton.Create(chart_id, "ChangeFontButton", 0, 95, 95, 230, 115))
    {
        Print("Failed to create change font button");
        return false;
    }
    changeFontButton.Text("Font<>");
    adminPanel.Add(changeFontButton);

    // Toggle theme button
    if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", 0, 5, 95, 90, 115))
    {
        Print("Failed to create toggle theme button");
        return false;
    }
    toggleThemeButton.Text("Theme<>");
    adminPanel.Add(toggleThemeButton);

    // Minimize button
    if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) // Adjusted Y-coordinate for visibility
    {
        Print("Failed to create minimize button");
        return false;
    }
    minimizeButton.Text("_");
    adminPanel.Add(minimizeButton);

    // Maximize button
    if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) // Adjusted Y-coordinate for visibility
    {
        Print("Failed to create maximize button");
        return false;
    }
    maximizeButton.Text("[ ]");
    adminPanel.Add(maximizeButton);

    // Close button for admin panel
    if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) // Adjusted Y-coordinate for visibility
    {
        Print("Failed to create close button");
        return false;
    }
    closeButton.Text("X");
    adminPanel.Add(closeButton);

    // Quick messages
    return CreateQuickMessageButtons();
}

//+------------------------------------------------------------------+
//| Create quick message buttons                                     |
//+------------------------------------------------------------------+
bool CreateQuickMessageButtons()
{
    string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
    int startX = 5, startY = 160, width = 222, height = 65, spacing = 5;

    for (int i = 0; i < 8; i++)
    {
        if (!quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + 1), 0, startX + (i % 2) * (width + spacing), startY + (i / 2) * (height + spacing), startX + (i % 2) * (width + spacing) + width, startY + (i / 2) * (height + spacing) + height))
        {
            Print("Failed to create quick message button ", i + 1);
            return false;
        }
        quickMessageButtons[i].Text(quickMessages[i]);
        adminPanel.Add(quickMessageButtons[i]);
    }
    return true;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    adminPanel.Destroy();
    Print("Deinitialization complete");
}

//+------------------------------------------------------------------+
//| Handle custom message send button click                          |
//+------------------------------------------------------------------+
void OnSendButtonClick()
{
    string message = inputBox.Text();
    if (message != "")
    {
        if (SendMessageToTelegram(message))
            Print("Custom message sent: ", message);
        else
            Print("Failed to send custom message.");
    }
    else
    {
        Print("No message entered.");
    }
}

//+------------------------------------------------------------------+
//| Handle clear button click                                        |
//+------------------------------------------------------------------+
void OnClearButtonClick()
{
    inputBox.Text("");
    OnInputChange();
    Print("Input box cleared.");
}

//+------------------------------------------------------------------+
//| Handle quick message button click                                |
//+------------------------------------------------------------------+
void OnQuickMessageButtonClick(int index)
{
    string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 };
    string message = quickMessages[index];

    if (SendMessageToTelegram(message))
        Print("Quick message sent: ", message);
    else
        Print("Failed to send quick message.");
}

//+------------------------------------------------------------------+
//| Update character counter                                         |
//+------------------------------------------------------------------+
void OnInputChange()
{
    int currentLength = StringLen(inputBox.Text());
    charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH));
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Handle toggle theme button click                                 |
//+------------------------------------------------------------------+
void OnToggleThemeButtonClick()
{
    darkTheme = !darkTheme;
    UpdateThemeColors();
    Print("Theme toggled: ", darkTheme ? "Dark" : "Light");
}

//+------------------------------------------------------------------+
//| Update theme colors for the panel                                |
//+------------------------------------------------------------------+
void UpdateThemeColors()
{
    // Use the dialog's theme update method as a placeholder.
    adminPanel.UpdateThemeColors(darkTheme);

    color textColor = darkTheme ? clrWhite : clrBlack;
    color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro;
    color borderColor = darkTheme ? clrSlateGray : clrGray;
    color bgColor     = darkTheme ? clrDarkBlue : clrWhite;

    inputBox.SetTextColor(textColor);
    inputBox.SetBackgroundColor(bgColor);
    inputBox.SetBorderColor(borderColor);

    UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor);
    UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor);

    for (int i = 0; i < ArraySize(quickMessageButtons); i++)
    {
        UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor);
    }

    charCounter.Color(textColor);

    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Apply theme settings to a button                                 |
//+------------------------------------------------------------------+
void UpdateButtonTheme(CButton &button, color textColor, color bgColor, color borderColor)
{
    button.SetTextColor(textColor);
    button.SetBackgroundColor(bgColor);
    button.SetBorderColor(borderColor);
}

//+------------------------------------------------------------------+
//| Handle change font button click                                  |
//+------------------------------------------------------------------+
void OnChangeFontButtonClick()
{
    currentFontIndex = (currentFontIndex + 1) % ArraySize(availableFonts);

    inputBox.Font(availableFonts[currentFontIndex]);
    clearButton.Font(availableFonts[currentFontIndex]);
    sendButton.Font(availableFonts[currentFontIndex]);
    toggleThemeButton.Font(availableFonts[currentFontIndex]);
    changeFontButton.Font(availableFonts[currentFontIndex]);

    for (int i = 0; i < ArraySize(quickMessageButtons); i++)
    {
        quickMessageButtons[i].Font(availableFonts[currentFontIndex]);
    }

    Print("Font changed to: ", availableFonts[currentFontIndex]);
    ChartRedraw();
}

//+------------------------------------------------------------------+
//| Handle minimize button click                                     |
//+------------------------------------------------------------------+
void OnMinimizeButtonClick()
{
    minimized = true;
    adminPanel.Hide();
    minimizeButton.Hide();
    maximizeButton.Show();
    closeButton.Show();
    Print("Panel minimized.");
}

//+------------------------------------------------------------------+
//| Handle maximize button click                                     |
//+------------------------------------------------------------------+
void OnMaximizeButtonClick()
{
    if (minimized)
    {
        adminPanel.Show();
        minimizeButton.Show();
        maximizeButton.Hide();
        closeButton.Hide();
        Print("Panel maximized.");
    }
}

//+------------------------------------------------------------------+
//| Handle close button click for admin panel                        |
//+------------------------------------------------------------------+
void OnCloseButtonClick()
{
    ExpertRemove();
    Print("Admin Panel closed.");
}

//+------------------------------------------------------------------+
//| Send the message to Telegram                                     |
//+------------------------------------------------------------------+
bool SendMessageToTelegram(string message)
{
    string url = "https://api.telegram.org/bot" + InputBotToken + "/sendMessage";
    string jsonMessage = "{\"chat_id\":\"" + InputChatId + "\", \"text\":\"" + message + "\"}";
    char post_data[];
    ArrayResize(post_data, StringToCharArray(jsonMessage, post_data, 0, WHOLE_ARRAY) - 1);

    int timeout = 5000;
    char result[];
    string responseHeaders;

    int res = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, post_data, result, responseHeaders);

    if (res == 200)
    {
        Print("Message sent successfully: ", message);
        return true;
    }
    else
    {
        Print("Failed to send message. HTTP code: ", res, " Error code: ", GetLastError());
        Print("Response: ", CharArrayToString(result));
        return false;
    }
}


Pruebas y resultados

Nuestro código se compiló correctamente y, al iniciar la aplicación, observamos que todas las funciones del panel permanecen inaccesibles hasta que se introduce el PIN correcto. Este comportamiento garantiza que solo los usuarios autorizados puedan acceder a las funciones administrativas. En esta etapa, estamos orgullosos de nuestro progreso, pero reconocemos que aún no hemos alcanzado los límites de nuestro desarrollo. Entendemos que nuestras medidas de seguridad aún deben mejorarse, ya que pueden ser vulnerables a hackers avanzados. Sabemos que cada paso que damos es una oportunidad para aprender más sobre la implementación del lenguaje MQL5 y, a medida que avanzamos en nuestras habilidades, podemos alcanzar niveles de seguridad más sólidos. A continuación se muestra una imagen que muestra el lanzamiento de la aplicación junto con el resultado deseado.

Panel de administración seguro

Lanzamiento del panel



Conclusión

En este proyecto, la implementación de un mecanismo de autenticación de inicio de sesión mejoró significativamente la seguridad del Panel de administración, lo cual es vital para proteger las funcionalidades confidenciales. Al exigir una contraseña antes de conceder acceso a las funciones de administración, el programa mitiga el uso no autorizado y garantiza que solo los usuarios verificados puedan gestionar ajustes y operaciones cruciales. El diseño se refuerza con una contraseña global claramente definida y una interfaz fácil de usar para introducir las credenciales.

A medida que avanzamos en nuestro Panel de administración, nos centraremos en mejoras críticas, como la transición de contraseñas codificadas a credenciales gestionadas de forma segura para evitar vulnerabilidades, la incorporación de la autenticación multifactorial para mayor seguridad y la optimización continua de la experiencia de inicio de sesión.

Por otro lado, reconocemos que una vez compilado el código, resulta difícil para cualquiera que no tenga acceso al código fuente acceder a él, gracias a las funciones de seguridad contra la descompilación que ofrece MQL5. Esta capa adicional de protección ayuda a proteger nuestra aplicación contra el acceso no autorizado y la ingeniería inversa.

¡No dudes en probarlo en tus proyectos! Agradezco los comentarios y opiniones, ya que sus aportaciones nos ayudan a mejorar y perfeccionar nuestro trabajo. Sus opiniones son muy valiosas para nosotros, ya que nos ayudan a seguir desarrollando y mejorando nuestras aplicaciones. Consulte el archivo adjunto a continuación.

Volver a la página de contenido

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16079

Archivos adjuntos |
Admin_Panel_.mq5 (18.66 KB)
SERGEI NAIDENOV
SERGEI NAIDENOV | 23 may 2025 en 19:53

Al intentar compilar:

'Admin_Panel.mq5' 1

Comercio.mqh

Objeto.mqh

StdLibErr.mqh

OrderInfo.mqh

HistoryOrderInfo.mqh

InformaciónPosición.mqh

DealInfo.mqh

Dialog.mqh

WndContainer.mqh

Wnd.mqh

Rect.mqh

Defines.mqh

ArrayObj.mqh

Array.mqh

WndClient.mqh

Panel.mqh

WndObj.mqh

ChartObjectsTxtControls.mqh

ChartObject.mqh

Scrolls.mqh

BmpButton.mqh

ChartObjectsBmpControls.mqh

Editar.mqh

Gráfico.mqh

Button.mqh

Label.mqh

Up.bmp' como recurso "::res\Up.bmp" 1

ThumbVert.bmp' como recurso "::res\ThumbVert.bmp" 1

Down.bmp' como recurso "::res\Down.bmp" 1

Left.bmp' como recurso "::resLeft.bmp" 1

ThumbHor.bmp' como recurso "::res\ThumbHor.bmp" 1

Right.bmp' como recurso "::res\Right.bmp" 1

Close.bmp' como recurso "::res\Close.bmp" 1

Restore.bmp' como recurso "::res\Restore.bmp" 1

Turn.bmp' como recurso "::res\Turn.bmp" 1

posible pérdida de datos debido a la conversión de tipo de 'long' a 'int' Admin_Panel(4)_.mq5 161 49

'UpdateThemeColors' - identificador no declarado Admin_Panel(4)_.mq5 390 16

darkTheme' - operador esperado Admin_Panel(4)_.mq5 390 34

SetTextColor' - identificador no declarado Admin_Panel(4)_.mq5 397 14

textColor' - operador esperado Admin_Panel(4)_.mq5 397 27

SetBackgroundColor' - identificador no declarado Admin_Panel(4)_.mq5 398 14

bgColor' - operador esperado Admin_Panel(4)_.mq5 398 33

SetBorderColor' - identificador no declarado Admin_Panel(4)_.mq5 399 14

borderColor' - operador esperado Admin_Panel(4)_.mq5 399 29

SetTextColor' - identificador no declarado Admin_Panel(4)_.mq5 424 12

textColor' - operador esperado Admin_Panel(4)_.mq5 424 25

SetBackgroundColor' - identificador no declarado Admin_Panel(4)_.mq5 425 12

bgColor' - operador esperado Admin_Panel(4)_.mq5 425 31

SetBorderColor' - identificador no declarado Admin_Panel(4)_.mq5 426 12

'borderColor' - se esperaba algún operador Admin_Panel(4)_.mq5 426 27

14 errores, 1 advertencias 15 2


Integración de Smart Money Concepts (SMC), Order Blocks (OB) y Fibonacci para entradas óptimas Integración de Smart Money Concepts (SMC), Order Blocks (OB) y Fibonacci para entradas óptimas
Los bloques de órdenes (Order Blocks, OB) son áreas clave donde los operadores institucionales inician compras o ventas significativas. Después de un movimiento de precio significativo, Fibonacci ayuda a identificar un retroceso potencial desde un máximo reciente hasta un mínimo para identificar la entrada comercial óptima.
Kit de herramientas de negociación MQL5 (Parte 3): Desarrollo de una biblioteca EX5 para la gestión de órdenes pendientes Kit de herramientas de negociación MQL5 (Parte 3): Desarrollo de una biblioteca EX5 para la gestión de órdenes pendientes
Aprenda a desarrollar e implementar una biblioteca EX5 integral de órdenes pendientes en su código o proyectos MQL5. Este artículo le mostrará cómo crear una extensa biblioteca EX5 de gestión de órdenes pendientes y lo guiará en el proceso de importarla e implementarla mediante la creación de un panel de negociación o una interfaz gráfica de usuario (GUI). El panel de órdenes del asesor experto permitirá a los usuarios abrir, monitorear y eliminar órdenes pendientes asociadas con un número mágico específico directamente desde la interfaz gráfica en la ventana del gráfico.
El análisis volumétrico de redes neuronales como clave de las tendencias futuras El análisis volumétrico de redes neuronales como clave de las tendencias futuras
Este artículo explora la posibilidad de mejorar la previsión de los precios usando como base el análisis comercial volumétrico mediante la integración de los principios del análisis técnico con la arquitectura de redes neuronales LSTM. Prestaremos especial atención a la detección e interpretación de volúmenes anómalos, el uso de clusterización y la generación y definición de características basadas en el volumen en el contexto del aprendizaje automático.
Integración de MQL5 con paquetes de procesamiento de datos (Parte 3): Visualización mejorada de datos Integración de MQL5 con paquetes de procesamiento de datos (Parte 3): Visualización mejorada de datos
En este artículo, realizaremos una visualización de datos mejorada que va más allá de los gráficos básicos, incorporando características como interactividad, datos en capas y elementos dinámicos, lo que permite a los operadores explorar tendencias, patrones y correlaciones de manera más eficaz.